From 6cf61e62c00795f49645d5bcbf3629b2d2e7438c Mon Sep 17 00:00:00 2001 From: khanhduytran0 Date: Thu, 6 Jul 2023 12:56:25 +0700 Subject: [PATCH] Feats: preference isolation & profile management - Rewrote LauncherPreferences -> PLPreferences + Now the file layout is the same as UI layout + (get/set)Preference are replaced with corresponding get/set types + Getting/setting is now done through keyPath instead of manually iterating over subdictionaries. + Implemented an isolation layer, allowing an instance to have its own settings. This is off by default - Implemented launcher_profiles + Added Profile manager that can be accessed from the sidebar + Moved "Game directory (instances)" to this screen + Replaced version selector with profile selector. Version selector is moved into profile editor. - Other changes + Cleanup code by using NSPredicate instead of huge for loops + Titles in settings are made multilined + Controller config: replaced bar button items with localized ones Due to how large the changes are, this is committed directly to the mainline branch. Bugs are expected to be found (although I did enough tests for this locally) --- .../main/java/net/kdt/pojavlaunch/Tools.java | 3 +- .../kdt/pojavlaunch/utils/MCOptionUtils.java | 4 +- Natives/AccountListViewController.m | 8 +- .../DefaultProfile.imageset/Contents.json | 12 + .../DefaultProfile.imageset/icon.png | Bin 0 -> 8738 bytes .../MenuProfiles.imageset/Contents.json | 12 + .../MenuProfiles.imageset/icon.png | Bin 0 -> 479 bytes Natives/CMakeLists.txt | 5 + Natives/CustomControlsViewController.h | 6 + Natives/CustomControlsViewController.m | 11 +- Natives/GameSurfaceView.m | 5 +- Natives/JavaGUIViewController.m | 8 +- Natives/JavaLauncher.m | 52 +-- Natives/LauncherMenuViewController.h | 4 + Natives/LauncherMenuViewController.m | 78 +++-- Natives/LauncherNavigationController.h | 4 +- Natives/LauncherNavigationController.m | 202 +++++------- Natives/LauncherNewsViewController.m | 7 +- Natives/LauncherPrefContCfgViewController.m | 24 +- Natives/LauncherPrefGameDirViewController.m | 21 +- Natives/LauncherPrefManageJREViewController.m | 8 +- Natives/LauncherPreferences.h | 31 +- Natives/LauncherPreferences.m | 236 +++++--------- Natives/LauncherPreferencesViewController.m | 62 +--- Natives/LauncherProfileEditorViewController.h | 6 + Natives/LauncherProfileEditorViewController.m | 304 ++++++++++++++++++ Natives/LauncherProfilesViewController.h | 6 + Natives/LauncherProfilesViewController.m | 222 +++++++++++++ Natives/LauncherSplitViewController.m | 8 +- Natives/MinecraftResourceUtils.h | 2 +- Natives/MinecraftResourceUtils.m | 32 +- Natives/PLPickerView.h | 15 + Natives/PLPickerView.m | 15 + Natives/PLPrefTableViewController.h | 7 +- Natives/PLPrefTableViewController.m | 87 ++--- Natives/PLPreferences.h | 16 + Natives/PLPreferences.m | 260 +++++++++++++++ Natives/PLProfiles.h | 22 ++ Natives/PLProfiles.m | 96 ++++++ Natives/SceneExternalDelegate.m | 4 +- Natives/SurfaceViewController+Navigation.m | 15 +- Natives/SurfaceViewController.m | 82 ++--- Natives/UIKit+hook.h | 10 + Natives/UIKit+hook.m | 4 +- Natives/authenticator/BaseAuthenticator.m | 2 +- Natives/customcontrols/ControlButton.m | 8 +- Natives/customcontrols/ControlLayout.m | 4 +- Natives/customcontrols/CustomControlsUtils.m | 4 +- Natives/input/ControllerInput.m | 4 +- Natives/ios_uikit_bridge.m | 8 +- Natives/main.m | 34 +- .../resources/en.lproj/Localizable.strings | 13 + 52 files changed, 1514 insertions(+), 579 deletions(-) create mode 100644 Natives/Assets.xcassets/DefaultProfile.imageset/Contents.json create mode 100644 Natives/Assets.xcassets/DefaultProfile.imageset/icon.png create mode 100644 Natives/Assets.xcassets/MenuProfiles.imageset/Contents.json create mode 100644 Natives/Assets.xcassets/MenuProfiles.imageset/icon.png create mode 100644 Natives/LauncherProfileEditorViewController.h create mode 100644 Natives/LauncherProfileEditorViewController.m create mode 100644 Natives/LauncherProfilesViewController.h create mode 100644 Natives/LauncherProfilesViewController.m create mode 100644 Natives/PLPickerView.h create mode 100644 Natives/PLPickerView.m create mode 100644 Natives/PLPreferences.h create mode 100644 Natives/PLPreferences.m create mode 100644 Natives/PLProfiles.h create mode 100644 Natives/PLProfiles.m diff --git a/JavaApp/src/main/java/net/kdt/pojavlaunch/Tools.java b/JavaApp/src/main/java/net/kdt/pojavlaunch/Tools.java index 38aa3249..ec5f5c8a 100644 --- a/JavaApp/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/JavaApp/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -52,6 +52,7 @@ public final class Tools public static final String DIR_GAME_HOME = System.getenv("POJAV_HOME"); public static final String DIR_GAME_NEW = System.getenv("POJAV_GAME_DIR"); // path to "Library/Application Support/minecraft" + public static final String DIR_GAME_PROFILE = System.getProperty("user.dir"); public static final String DIR_APP_DATA = System.getenv("POJAV_HOME"); public static final String DIR_ACCOUNT_NEW = DIR_APP_DATA + "/accounts"; @@ -120,7 +121,7 @@ public final class Tools versionName = versionInfo.inheritsFrom; } - File gameDir = new File(Tools.DIR_GAME_NEW); + File gameDir = new File(Tools.DIR_GAME_PROFILE); gameDir.mkdirs(); Map varArgMap = new ArrayMap(); diff --git a/JavaApp/src/main/java/net/kdt/pojavlaunch/utils/MCOptionUtils.java b/JavaApp/src/main/java/net/kdt/pojavlaunch/utils/MCOptionUtils.java index f1602ed3..f876b296 100644 --- a/JavaApp/src/main/java/net/kdt/pojavlaunch/utils/MCOptionUtils.java +++ b/JavaApp/src/main/java/net/kdt/pojavlaunch/utils/MCOptionUtils.java @@ -17,7 +17,7 @@ public class MCOptionUtils } try { - BufferedReader reader = new BufferedReader(new FileReader(Tools.DIR_GAME_NEW + "/options.txt")); + BufferedReader reader = new BufferedReader(new FileReader(Tools.DIR_GAME_PROFILE + "/options.txt")); String line; while ((line = reader.readLine()) != null) { mLineList.add(line); @@ -71,7 +71,7 @@ public class MCOptionUtils } try { - Tools.write(Tools.DIR_GAME_NEW + "/options.txt", result.toString()); + Tools.write(Tools.DIR_GAME_PROFILE + "/options.txt", result.toString()); } catch (IOException e) { System.err.println("Could not save options.txt"); e.printStackTrace(); diff --git a/Natives/AccountListViewController.m b/Natives/AccountListViewController.m index 1f002f71..4257a174 100644 --- a/Natives/AccountListViewController.m +++ b/Natives/AccountListViewController.m @@ -162,8 +162,8 @@ } - (void)actionLoginLocal:(UIView *)sender { - if ([getPreference(@"local_warn") boolValue] == YES) { - setPreference(@"local_warn", @NO); + if (getPrefBool(@"warnings.local_warn")) { + setPrefBool(@"warnings.local_warn", NO); UIAlertController *alert = [UIAlertController alertControllerWithTitle:localize(@"login.warn.title.localmode", nil) message:localize(@"login.warn.message.localmode", nil) preferredStyle:UIAlertControllerStyleActionSheet]; alert.popoverPresentationController.sourceView = sender; alert.popoverPresentationController.sourceRect = sender.bounds; @@ -186,8 +186,8 @@ [self presentViewController:controller animated:YES completion:nil]; } else { id callback = ^(id status, BOOL success) { - [self dismissViewControllerAnimated:YES completion:nil]; self.whenItemSelected(); + [self dismissViewControllerAnimated:YES completion:nil]; }; [[[LocalAuthenticator alloc] initWithInput:usernameField.text] loginWithCallback:callback]; } @@ -270,9 +270,9 @@ showDialog(localize(@"Error", nil), errorStr); } } else if (success) { + self.whenItemSelected(); [self removeActivityIndicatorFrom:cell]; [self dismissViewControllerAnimated:YES completion:nil]; - self.whenItemSelected(); } } diff --git a/Natives/Assets.xcassets/DefaultProfile.imageset/Contents.json b/Natives/Assets.xcassets/DefaultProfile.imageset/Contents.json new file mode 100644 index 00000000..1ffad00d --- /dev/null +++ b/Natives/Assets.xcassets/DefaultProfile.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Natives/Assets.xcassets/DefaultProfile.imageset/icon.png b/Natives/Assets.xcassets/DefaultProfile.imageset/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d318425c5ba2932857685ad79d9a091bf4d39a99 GIT binary patch literal 8738 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 UMg8vSg<1j9x~+#RQnLyDALyRoSpWb4 literal 0 HcmV?d00001 diff --git a/Natives/Assets.xcassets/MenuProfiles.imageset/Contents.json b/Natives/Assets.xcassets/MenuProfiles.imageset/Contents.json new file mode 100644 index 00000000..1ffad00d --- /dev/null +++ b/Natives/Assets.xcassets/MenuProfiles.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Natives/Assets.xcassets/MenuProfiles.imageset/icon.png b/Natives/Assets.xcassets/MenuProfiles.imageset/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..40b3727076071444b916de02eccbac7f844bcb78 GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU|iNGd03ECl^pgdc(_ z?Y5`7)r|-Fa@MW*poTFCPZcS!9m+^~zdExeM~WK~zj&?GIk}*VhZb zywE-Os;2Gp?{iY;UY<9e^DKxrU(y|BAkf?$Nvm%k`+2Fpee=E8qdSZCwSN9`knx~Jl)Z5Jo|iM)bm#s*3sgH^Y;F02tEjPC74>bV`r^o z`uo3z=L&3&TK>6mJaWgb=FhJ_@SfZ1Ejy?88&@C1p%C{Tz4y=1Z~5ILmNR(@^6uX_ zVkuQ~ 0) { + if (minVersion > preferredJavaVersion) { + NSLog(@"[JavaLauncher] Profile's preferred Java version (%d) does not meet the minimum version (%d), dropping request", preferredJavaVersion, minVersion); + } else { + NSDebugLog(@"[PLProfiles] Applying javaVersion"); + minVersion = preferredJavaVersion; + } + } if (minVersion <= 8) { defaultJRETag = @"1_16_5_older"; } else { defaultJRETag = @"1_17_newer"; } + + // Setup POJAV_RENDERER + NSString *renderer = [PLProfiles resolveKeyForCurrentProfile:@"renderer"]; + NSLog(@"[JavaLauncher] RENDERER is set to %@\n", renderer); + setenv("POJAV_RENDERER", renderer.UTF8String, 1); + // Setup gameDir + gameDir = [NSString stringWithFormat:@"%s/instances/%@/%@", + getenv("POJAV_HOME"), getPrefObject(@"general.game_directory"), + [PLProfiles resolveKeyForCurrentProfile:@"gameDir"]] + .stringByStandardizingPath; } else { defaultJRETag = @"install_jar"; + gameDir = @(getenv("POJAV_GAME_DIR")); } + NSLog(@"[JavaLauncher] Looking for Java %d or later", minVersion); NSString *javaHome = getSelectedJavaHome(defaultJRETag, minVersion); if (javaHome == nil) { UIKit_returnToSplitView(); BOOL isInstallJar = [defaultJRETag isEqualToString:@"install_jar"]; showDialog(localize(@"Error", nil), [NSString stringWithFormat:localize(@"java.error.missing_runtime", nil), - isInstallJar ? [launchTarget lastPathComponent] : getPreference(@"selected_version"), minVersion]); + isInstallJar ? [launchTarget lastPathComponent] : getPrefObject(@"internal.selected_version"), minVersion]); return 1; } else if ([javaHome hasPrefix:@(getenv("POJAV_HOME"))]) { // Activate Library Validation bypass for external runtime @@ -121,22 +145,12 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi setenv("JAVA_HOME", javaHome.UTF8String, 1); NSLog(@"[JavaLauncher] JAVA_HOME has been set to %@", javaHome); - NSString *renderer = getPreference(@"renderer"); - if (renderer.length == 0) { - renderer = @"auto"; - setPreference(@"renderer", renderer); - NSLog(@"[JavaLauncher] RENDERER environment variable was not set. Defaulting to %@ for future use.\n", renderer); - } else { - NSLog(@"[JavaLauncher] Restored preference: RENDERER is set to %@\n", renderer); - } - setenv("POJAV_RENDERER", renderer.UTF8String, 1); - int allocmem; - if ([getPreference(@"auto_ram") boolValue]) { + if (getPrefBool(@"java.auto_ram")) { CGFloat autoRatio = getEntitlementValue(@"com.apple.private.memorystatus") ? 0.4 : 0.25; allocmem = roundf((NSProcessInfo.processInfo.physicalMemory / 1048576) * autoRatio); } else { - allocmem = [getPreference(@"allocated_memory") intValue]; + allocmem = getPrefInt(@"java.allocated_memory"); } NSLog(@"[JavaLauncher] Max RAM allocation is set to %d MB", allocmem); @@ -150,7 +164,7 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi margv[++margc] = [NSString stringWithFormat:@"-Xmx%dM", allocmem].UTF8String; margv[++margc] = [NSString stringWithFormat:@"-Djava.library.path=%1$s/Frameworks", getenv("BUNDLE_PATH")].UTF8String; margv[++margc] = [NSString stringWithFormat:@"-Djna.boot.library.path=%s/Frameworks", getenv("BUNDLE_PATH")].UTF8String; - margv[++margc] = [NSString stringWithFormat:@"-Duser.dir=%s", getenv("POJAV_GAME_DIR")].UTF8String; + margv[++margc] = [NSString stringWithFormat:@"-Duser.dir=%@", gameDir].UTF8String; margv[++margc] = [NSString stringWithFormat:@"-Duser.home=%s", getenv("POJAV_HOME")].UTF8String; margv[++margc] = [NSString stringWithFormat:@"-Duser.timezone=%@", NSTimeZone.localTimeZone.name].UTF8String; margv[++margc] = [NSString stringWithFormat:@"-DUIScreen.maximumFramesPerSecond=%d", (int)UIScreen.mainScreen.maximumFramesPerSecond].UTF8String; @@ -160,7 +174,7 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi margv[++margc] = "-Dlog4j2.formatMsgNoLookups=true"; NSString *librariesPath = [NSString stringWithFormat:@"%s/libs", getenv("BUNDLE_PATH")]; - if([getPreference(@"cosmetica") boolValue]) { + if(getPrefBool(@"general.cosmetica")) { margv[++margc] = [NSString stringWithFormat:@"-javaagent:%@/arc_dns_injector.jar=23.95.137.176", librariesPath].UTF8String; } @@ -271,7 +285,7 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi margv[++margc] = [NSString stringWithFormat:@"%dx%d", width, height].UTF8String; pJLI_Launch = (JLI_Launch_func *)dlsym(libjli, "JLI_Launch"); - + if (NULL == pJLI_Launch) { NSLog(@"[Init] JLI_Launch = NULL"); return -2; diff --git a/Natives/LauncherMenuViewController.h b/Natives/LauncherMenuViewController.h index 91a93c31..d958a079 100644 --- a/Natives/LauncherMenuViewController.h +++ b/Natives/LauncherMenuViewController.h @@ -1,8 +1,12 @@ #import +#define sidebarNavController ((UINavigationController *)self.splitViewController.viewControllers[0]) +#define sidebarViewController ((LauncherMenuViewController *)sidebarNavController.viewControllers[0]) + @interface LauncherMenuCustomItem : NSObject @property(nonatomic) NSString *title, *imageName; @property(nonatomic, copy) void (^action)(void); +@property(nonatomic) NSArray *vcArray; @end @interface LauncherMenuViewController : UITableViewController diff --git a/Natives/LauncherMenuViewController.m b/Natives/LauncherMenuViewController.m index f45c831f..f5ba7079 100644 --- a/Natives/LauncherMenuViewController.m +++ b/Natives/LauncherMenuViewController.m @@ -7,7 +7,8 @@ #import "LauncherNewsViewController.h" #import "LauncherPreferences.h" #import "LauncherPreferencesViewController.h" -//#import "LauncherProfilesViewController.h" +#import "LauncherProfilesViewController.h" +#import "PLProfiles.h" #import "UIButton+AFNetworking.h" #import "UIImageView+AFNetworking.h" #import "UIKit+hook.h" @@ -26,11 +27,22 @@ return item; } ++ (LauncherMenuCustomItem *)vcClass:(Class)class { + id vc = [class new]; + LauncherMenuCustomItem *item = [[LauncherMenuCustomItem alloc] init]; + item.title = [vc title]; + item.imageName = [vc imageName]; + // View controllers are put into an array to keep its state + item.vcArray = @[vc]; + return item; +} + @end @interface LauncherMenuViewController() -@property(nonatomic) NSMutableArray *options; +@property(nonatomic) NSMutableArray *options; @property(nonatomic) UILabel *statusLabel; +@property(nonatomic) int lastSelectedIndex; @end @implementation LauncherMenuViewController @@ -47,11 +59,11 @@ self.navigationItem.titleView = titleView; [titleView sizeToFit]; - // View controllers are put into an array to keep its state - self.options = [NSMutableArray new]; - [self.options addObject:[LauncherNewsViewController new]]; - //[self.options addObject:[LauncherProfilesViewController new]]; - [self.options addObject:[LauncherPreferencesViewController new]]; + self.options = @[ + [LauncherMenuCustomItem vcClass:LauncherNewsViewController.class], + [LauncherMenuCustomItem vcClass:LauncherProfilesViewController.class], + [LauncherMenuCustomItem vcClass:LauncherPreferencesViewController.class], + ].mutableCopy; if (realUIIdiom != UIUserInterfaceIdiomTV) { [self.options addObject:(id)[LauncherMenuCustomItem title:localize(@"launcher.menu.custom_controls", nil) @@ -127,7 +139,7 @@ [self displayProgress:localize(@"login.jit.enabled", nil)]; [self displayProgress:nil]; } else { - [self enableJITWithJitStreamer:[getPreference(@"enable_altkit") boolValue]]; + [self enableJITWithJitStreamer:getPrefBool(@"general.enable_altkit")]; } } } @@ -156,8 +168,7 @@ - (void)restoreHighlightedSelection { // Restore the selected row when the view appears again - int index = [self.options indexOfObject:[contentNavigationController viewControllers][0]]; - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:MAX(0, index) inSection:0]; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.lastSelectedIndex inSection:0]; [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; } @@ -198,34 +209,36 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - UIViewController *selected = self.options[indexPath.row]; + LauncherMenuCustomItem *selected = self.options[indexPath.row]; - if ([selected isKindOfClass:UIViewController.class]) { + if (selected.action != nil) { + [self restoreHighlightedSelection]; + ((LauncherMenuCustomItem *)selected).action(); + } else { if(self.isInitialVc) { self.isInitialVc = NO; } else { - [contentNavigationController setViewControllers:@[selected] animated:NO]; + self.options[self.lastSelectedIndex].vcArray = contentNavigationController.viewControllers; + [contentNavigationController setViewControllers:selected.vcArray animated:NO]; + self.lastSelectedIndex = indexPath.row; } - selected.navigationItem.rightBarButtonItem = self.accountBtnItem; - selected.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; - selected.navigationItem.leftItemsSupplementBackButton = true; - } else { - [self restoreHighlightedSelection]; - ((LauncherMenuCustomItem *)selected).action(); + selected.vcArray[0].navigationItem.rightBarButtonItem = self.accountBtnItem; + selected.vcArray[0].navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; + selected.vcArray[0].navigationItem.leftItemsSupplementBackButton = true; } } - (void)selectAccount:(UIButton *)sender { AccountListViewController *vc = [[AccountListViewController alloc] init]; vc.whenDelete = ^void(NSString* name) { - if ([name isEqualToString:getPreference(@"selected_account")]) { + if ([name isEqualToString:getPrefObject(@"internal.selected_account")]) { BaseAuthenticator.current = nil; - setPreference(@"selected_account", @""); + setPrefObject(@"internal.selected_account", @""); [self updateAccountInfo]; } }; vc.whenItemSelected = ^void() { - setPreference(@"selected_account", BaseAuthenticator.current.authData[@"username"]); + setPrefObject(@"internal.selected_account", BaseAuthenticator.current.authData[@"username"]); [self updateAccountInfo]; if (sender != self.accountButton) { // Called from the play button, so call back to continue @@ -262,11 +275,13 @@ BOOL isDemo = [selected[@"username"] hasPrefix:@"Demo."]; NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:[selected[@"username"] substringFromIndex:(isDemo?5:0)]]; + // Check if we're switching between demo and full mode + BOOL shouldUpdateProfiles = (getenv("DEMO_LOCK")!=NULL) != isDemo; + // Reset states unsetenv("DEMO_LOCK"); setenv("POJAV_GAME_DIR", [NSString stringWithFormat:@"%s/Library/Application Support/minecraft", getenv("POJAV_HOME")].UTF8String, 1); - id subtitle; if (isDemo) { subtitle = localize(@"login.option.demo", nil); @@ -296,10 +311,16 @@ [self.accountButton.imageView setImageWithURL:url placeholderImage:placeholder]; [self.accountButton sizeToFit]; - // Update the version list, only if the selected type is Installed - int selectedVersionType = [getPreference(@"selected_version_type") intValue]; - if (selectedVersionType == 0) { - [contentNavigationController reloadVersionList:0]; + // Update profiles and local version list if needed + if (shouldUpdateProfiles) { + [contentNavigationController fetchLocalVersionList]; + [PLProfiles updateCurrent]; + } + + // Update tableView whenever we have + UITableViewController *tableVC = contentNavigationController.viewControllers.lastObject; + if ([tableVC isKindOfClass:UITableViewController.class]) { + [tableVC.tableView reloadData]; } } @@ -335,8 +356,7 @@ { [self displayProgress:localize(@"login.jit.checking", nil)]; - // TODO: customizable address - NSString *address = getPreference(@"jitstreamer_server"); + NSString *address = getPrefObject(@"general.jitstreamer_server"); NSLog(@"[JitStreamer] Server is %@, attempting to connect...", address); AFHTTPSessionManager *manager = AFHTTPSessionManager.manager; diff --git a/Natives/LauncherNavigationController.h b/Natives/LauncherNavigationController.h index 80469cc9..ff9cf202 100644 --- a/Natives/LauncherNavigationController.h +++ b/Natives/LauncherNavigationController.h @@ -1,6 +1,6 @@ #import -NSArray *remoteVersionList; +NSMutableArray *localVersionList, *remoteVersionList; @interface LauncherNavigationController : UINavigationController @@ -8,7 +8,7 @@ NSArray *remoteVersionList; @property(nonatomic) UILabel* progressText; @property(nonatomic) UIButton* buttonInstall; -- (void)reloadVersionList:(int)type; +- (void)fetchLocalVersionList; - (void)setInteractionEnabled:(BOOL)enable; @end diff --git a/Natives/LauncherNavigationController.m b/Natives/LauncherNavigationController.m index 68b38ec5..2a45fc49 100644 --- a/Natives/LauncherNavigationController.m +++ b/Natives/LauncherNavigationController.m @@ -8,24 +8,20 @@ #import "LauncherPreferences.h" #import "MinecraftResourceUtils.h" #import "PickTextField.h" +#import "PLPickerView.h" +#import "PLProfiles.h" #import "ios_uikit_bridge.h" #import "UIKit+hook.h" #import "utils.h" #define AUTORESIZE_MASKS UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin -#define sidebarNavController ((UINavigationController *)self.splitViewController.viewControllers[0]) -#define sidebarViewController ((LauncherMenuViewController *)sidebarNavController.viewControllers[0]) -@interface LauncherNavigationController () { +@interface LauncherNavigationController () { } -@property(nonatomic) UIView* fakeToolbar; - -@property(nonatomic) NSMutableArray* versionList; - -@property(nonatomic) UIPickerView* versionPickerView; +@property(nonatomic) PLPickerView* versionPickerView; @property(nonatomic) UITextField* versionTextField; -@property(nonatomic) int versionSelectedAt; +@property(nonatomic) int profileSelectedAt; @end @@ -39,37 +35,27 @@ [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; } - self.versionTextField = [[PickTextField alloc] initWithFrame:CGRectMake(0, 4, self.toolbar.frame.size.width * 0.8, self.toolbar.frame.size.height - 8)]; + self.versionTextField = [[PickTextField alloc] initWithFrame:CGRectMake(4, 4, self.toolbar.frame.size.width * 0.8 - 8, self.toolbar.frame.size.height - 8)]; [self.versionTextField addTarget:self.versionTextField action:@selector(resignFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit]; self.versionTextField.autoresizingMask = AUTORESIZE_MASKS; self.versionTextField.placeholder = @"Specify version..."; + self.versionTextField.leftView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; self.versionTextField.rightView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"SpinnerArrow"] _imageWithSize:CGSizeMake(30, 30)]]; self.versionTextField.rightView.frame = CGRectMake(0, 0, self.versionTextField.frame.size.height * 0.9, self.versionTextField.frame.size.height * 0.9); + self.versionTextField.leftViewMode = UITextFieldViewModeAlways; self.versionTextField.rightViewMode = UITextFieldViewModeAlways; - self.versionTextField.text = (NSString *) getPreference(@"selected_version"); self.versionTextField.textAlignment = NSTextAlignmentCenter; - self.versionList = [[NSMutableArray alloc] init]; - self.versionPickerView = [[UIPickerView alloc] init]; + self.versionPickerView = [[PLPickerView alloc] init]; self.versionPickerView.delegate = self; self.versionPickerView.dataSource = self; UIToolbar *versionPickToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, 44.0)]; - UISegmentedControl *versionTypeControl = [[UISegmentedControl alloc] initWithItems:@[ - localize(@"Installed", nil), - localize(@"Releases", nil), - localize(@"Snapshot", nil), - localize(@"Old-beta", nil), - localize(@"Old-alpha", nil) - ]]; - versionTypeControl.selectedSegmentIndex = [getPreference(@"selected_version_type") intValue]; - [versionTypeControl addTarget:self action:@selector(changeVersionType:) forControlEvents:UIControlEventValueChanged]; - [self reloadVersionList:versionTypeControl.selectedSegmentIndex]; + [self reloadProfileList]; - UIBarButtonItem *versionTypeItem = [[UIBarButtonItem alloc] initWithCustomView:versionTypeControl]; UIBarButtonItem *versionFlexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil]; UIBarButtonItem *versionDoneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(versionClosePicker)]; - versionPickToolbar.items = @[versionTypeItem, versionFlexibleSpace, versionDoneButton]; + versionPickToolbar.items = @[versionFlexibleSpace, versionDoneButton]; self.versionTextField.inputAccessoryView = versionPickToolbar; self.versionTextField.inputView = self.versionPickerView; @@ -103,6 +89,11 @@ self.progressText.userInteractionEnabled = NO; [targetToolbar addSubview:self.progressText]; + self.buttonInstall.enabled = NO; + + [self fetchLocalVersionList]; + [self fetchRemoteVersionList]; + if ([BaseAuthenticator.current isKindOfClass:MicrosoftAuthenticator.class]) { // Perform token refreshment on startup [self setInteractionEnabled:NO]; @@ -118,114 +109,74 @@ } } -- (void)viewWillAppear:(BOOL)animated { - if (self.fakeToolbar != nil) { - // Make this view appear on top of the real one - [self.view addSubview:self.fakeToolbar]; - } -} - -- (BOOL)isVersionInstalled:(NSString *)versionId -{ +- (BOOL)isVersionInstalled:(NSString *)versionId { NSString *localPath = [NSString stringWithFormat:@"%s/versions/%@", getenv("POJAV_GAME_DIR"), versionId]; BOOL isDirectory; - NSFileManager *fileManager = [NSFileManager defaultManager]; - [fileManager fileExistsAtPath:localPath isDirectory:&isDirectory]; + [NSFileManager.defaultManager fileExistsAtPath:localPath isDirectory:&isDirectory]; return isDirectory; } -- (void)fetchLocalVersionList -{ +- (void)fetchLocalVersionList { + if (!localVersionList) { + localVersionList = [NSMutableArray new]; + } + [localVersionList removeAllObjects]; + NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *versionPath = [NSString stringWithFormat:@"%s/versions/", getenv("POJAV_GAME_DIR")]; - NSArray *localVersionList = [fileManager contentsOfDirectoryAtPath:versionPath error:Nil]; - for (NSString *versionId in localVersionList) { + NSArray *list = [fileManager contentsOfDirectoryAtPath:versionPath error:Nil]; + for (NSString *versionId in list) { if (![self isVersionInstalled:versionId]) continue; - [self.versionList addObject:versionId]; - if ([self.versionTextField.text isEqualToString:versionId]) { - self.versionSelectedAt = self.versionList.count - 1; - } + [localVersionList addObject:@{ + @"id": versionId, + @"type": @"custom" + }]; } } -- (void)reloadVersionList:(int)type -{ - __block NSObject *lastSelected = nil; - if (self.versionList.count > 0 && self.versionSelectedAt >= 0) { - lastSelected = self.versionList[self.versionSelectedAt]; - } - [self.versionList removeAllObjects]; - - void(^reloadCompletion)(void) = ^{ - [self.versionPickerView reloadAllComponents]; - - if (self.versionSelectedAt < 0 && lastSelected != nil) { - NSObject *nearest = [MinecraftResourceUtils findNearestVersion:lastSelected expectedType:type]; - if (nearest != nil) { - self.versionSelectedAt = [self.versionList indexOfObject:nearest]; - } - } - lastSelected = nil; - - // Get back the currently selected in case none matching version found - self.versionSelectedAt = MIN(abs(self.versionSelectedAt), self.versionList.count - 1); - - [self.versionPickerView selectRow:self.versionSelectedAt inComponent:0 animated:NO]; - [self pickerView:self.versionPickerView didSelectRow:self.versionSelectedAt inComponent:0]; - - self.buttonInstall.enabled = YES; - self.progressViewMain.progress = 0; - }; - - if (type == TYPE_INSTALLED) { - [self fetchLocalVersionList]; - } - +- (void)fetchRemoteVersionList { self.buttonInstall.enabled = NO; + remoteVersionList = @[ + @{@"id": @"latest-release", @"type": @"release"}, + @{@"id": @"latest-snapshot", @"type": @"snapshot"} + ].mutableCopy; AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [manager GET:@"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" parameters:nil headers:nil progress:^(NSProgress * _Nonnull progress) { self.progressViewMain.progress = progress.fractionCompleted; } success:^(NSURLSessionTask *task, NSDictionary *responseObject) { - remoteVersionList = responseObject[@"versions"]; - assert(remoteVersionList != nil); - self.versionSelectedAt = -self.versionSelectedAt; - - if (type == TYPE_INSTALLED) { - reloadCompletion(); - return; - } - - for (NSDictionary *versionInfo in remoteVersionList) { - NSString *versionId = versionInfo[@"id"]; - NSString *versionType = versionInfo[@"type"]; - if (([versionType isEqualToString:@"release"] && type == TYPE_RELEASE) || - ([versionType isEqualToString:@"snapshot"] && type == TYPE_SNAPSHOT) || - ([versionType isEqualToString:@"old_beta"] && type == TYPE_OLDBETA) || - ([versionType isEqualToString:@"old_alpha"] && type == TYPE_OLDALPHA)) { - [self.versionList addObject:versionInfo]; - if ([self.versionTextField.text isEqualToString:versionId]) { - self.versionSelectedAt = self.versionList.count - 1; - } - } - } - - reloadCompletion(); + [remoteVersionList addObjectsFromArray:responseObject[@"versions"]]; + NSDebugLog(@"[VersionList] Got %d versions", remoteVersionList.count); + setPrefObject(@"internal.latest_version", responseObject[@"latest"]); + [self fetchLocalVersionList]; + self.buttonInstall.enabled = YES; } failure:^(NSURLSessionTask *operation, NSError *error) { NSDebugLog(@"[VersionList] Warning: Unable to fetch version list: %@", error.localizedDescription); - reloadCompletion(); + self.buttonInstall.enabled = YES; }]; } -- (void)changeVersionType:(UISegmentedControl *)sender { - setPreference(@"selected_version_type", @(sender.selectedSegmentIndex)); - [self reloadVersionList:sender.selectedSegmentIndex]; +// [tmp cmt] Invoked by: startup, instance change event +- (void)reloadProfileList { + // Reload launcher_profiles.json + [PLProfiles updateCurrent]; + [self.versionPickerView reloadAllComponents]; + // Reload selected profile info + self.profileSelectedAt = [PLProfiles.current.profiles.allKeys indexOfObject:PLProfiles.current.selectedProfileName]; + [self.versionPickerView selectRow:self.profileSelectedAt inComponent:0 animated:NO]; + [self pickerView:self.versionPickerView didSelectRow:self.profileSelectedAt inComponent:0]; } #pragma mark - Options - (void)enterCustomControls { CustomControlsViewController *vc = [[CustomControlsViewController alloc] init]; vc.modalPresentationStyle = UIModalPresentationOverFullScreen; + vc.setDefaultCtrl = ^(NSString *name){ + setPrefObject(@"control.default_ctrl", name); + }; + vc.getDefaultCtrl = ^{ + return getPrefObject(@"control.default_ctrl"); + }; [self presentViewController:vc animated:YES completion:nil]; } @@ -263,7 +214,7 @@ } - (void)launchMinecraft:(UIButton *)sender { - if (!self.versionTextField.hasText || self.versionList.count == 0) { + if (!self.versionTextField.hasText) { [self.versionTextField becomeFirstResponder]; return; } @@ -279,7 +230,11 @@ sender.alpha = 0.5; [self setInteractionEnabled:NO]; - NSObject *object = self.versionList[[self.versionPickerView selectedRowInComponent:0]]; + NSString *versionId = PLProfiles.current.profiles[self.versionTextField.text][@"lastVersionId"]; + NSDictionary *object = [remoteVersionList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(id == %@)", versionId]].firstObject; + if (!object) { + object = [localVersionList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(id == %@)", versionId]].firstObject; + } [MinecraftResourceUtils downloadVersion:object callback:^(NSString *stage, NSProgress *mainProgress, NSProgress *progress) { if (progress == nil && stage != nil) { @@ -307,14 +262,14 @@ } - (void)invokeAfterJITEnabled:(void(^)(void))handler { - remoteVersionList = nil; + localVersionList = remoteVersionList = nil; if (isJITEnabled(false)) { dispatch_async(dispatch_get_main_queue(), ^{ handler(); }); return; - } else if ([getPreference(@"debug_skip_wait_jit") boolValue]) { + } else if (getPrefBool(@"debug.debug_skip_wait_jit")) { NSLog(@"Debug option skipped waiting for JIT. Java might not work."); handler(); return; @@ -350,32 +305,39 @@ } #pragma mark - UIPickerView stuff -- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { +- (void)pickerView:(PLPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { + self.profileSelectedAt = row; +/* if (self.versionList.count == 0) { self.versionTextField.text = @""; return; } - self.versionSelectedAt = row; +*/ + ((UIImageView *)self.versionTextField.leftView).image = [pickerView imageAtRow:row column:component]; self.versionTextField.text = [self pickerView:pickerView titleForRow:row forComponent:component]; - setPreference(@"selected_version", self.versionTextField.text); + PLProfiles.current.selectedProfileName = self.versionTextField.text; } -- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView { +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 1; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { - return self.versionList.count; + return PLProfiles.current.profiles.count; } - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { - if (self.versionList.count <= row) return nil; - NSObject *object = self.versionList[row]; - if ([object isKindOfClass:[NSString class]]) { - return (NSString*) object; - } else { - return [object valueForKey:@"id"]; + return PLProfiles.current.profiles.allValues[row][@"name"]; +} + +- (UIImage *)pickerView:(UIPickerView *)pickerView imageForRow:(NSInteger)row forComponent:(NSInteger)component { + NSString *urlString = PLProfiles.current.profiles.allValues[row][@"icon"]; + if (!urlString) { + return [[UIImage imageNamed:@"DefaultProfile"] _imageWithSize:CGSizeMake(40, 40)]; } + NSString *iconStr = [urlString stringByReplacingOccurrencesOfString:@"data:image/png;base64," withString:@""]; + NSData *iconData = [[NSData alloc] initWithBase64EncodedString:iconStr options:NSDataBase64DecodingIgnoreUnknownCharacters]; + return [[UIImage imageWithData:iconData] _imageWithSize:CGSizeMake(40, 40)]; } - (void)versionClosePicker { diff --git a/Natives/LauncherNewsViewController.m b/Natives/LauncherNewsViewController.m index cce041b8..62a8295d 100644 --- a/Natives/LauncherNewsViewController.m +++ b/Natives/LauncherNewsViewController.m @@ -4,9 +4,6 @@ #import "LauncherPreferences.h" #import "utils.h" -#define sidebarNavController ((UINavigationController *)self.splitViewController.viewControllers[0]) -#define sidebarViewController ((LauncherMenuViewController *)sidebarNavController.viewControllers[0]) - @interface LauncherNewsViewController() @end @@ -48,7 +45,7 @@ UIEdgeInsets insets; [webView loadRequest:request]; [self.view addSubview:webView]; - if(!getenv("POJAV_DETECTEDJB") && [getPreference(@"limited_ram_warn") boolValue] == YES && (roundf(NSProcessInfo.processInfo.physicalMemory / 1048576) < 32900)) { + if(!getenv("POJAV_DETECTEDJB") && getPrefBool(@"warnings.limited_ram_warn") && (roundf(NSProcessInfo.processInfo.physicalMemory / 1048576) < 32900)) { // "This device has a limited amount of memory available." [self showWarningAlert:@"limited_ram" hasPreference:YES]; } @@ -67,7 +64,7 @@ UIEdgeInsets insets; UIAlertAction *action; if(isPreferenced) { action = [UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) { - setPreference([NSString stringWithFormat:@"%@_warn", key], @NO); + setPrefBool([NSString stringWithFormat:@"warnings.%@_warn", key], NO); }]; } else { action = [UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleCancel handler:nil]; diff --git a/Natives/LauncherPrefContCfgViewController.m b/Natives/LauncherPrefContCfgViewController.m index 91f468fa..0ec097e1 100644 --- a/Natives/LauncherPrefContCfgViewController.m +++ b/Natives/LauncherPrefContCfgViewController.m @@ -53,8 +53,8 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); self.prefSections = @[@"config_files", @"game_mappings", @"menu_mappings", @"controller_style"]; - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:localize(@"Save", nil) style:UIBarButtonItemStyleDone target:self action:@selector(actionMenuSave)]; - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:localize(@"Exit", nil) style:UIBarButtonItemStyleDone target:self action:@selector(exitButtonSelector)]; + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(actionMenuSave)]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemClose target:self action:@selector(exitButtonSelector)]; self.editPickMapping = [[UIPickerView alloc] init]; self.editPickMapping.delegate = self; @@ -66,9 +66,9 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); } - (void)loadGamepadConfigurationFile { - NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/%@", getenv("POJAV_HOME"), getPreference(@"default_gamepad_ctrl")]; + NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/%@", getenv("POJAV_HOME"), getPrefObject(@"control.default_gamepad_ctrl")]; self.currentMappings = parseJSONFromFile(gamepadPath); - self.currentFileName = [getPreference(@"default_ctrl") stringByDeletingPathExtension]; + self.currentFileName = [getPrefObject(@"control.default_ctrl") stringByDeletingPathExtension]; NSPredicate *filterPredicate = [NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *dict) { return ![obj[@"name"] hasPrefix:@"mouse_"]; }]; @@ -119,10 +119,10 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); if(indexPath.section == 0) { cell.textLabel.text = localize(@"controller_configurator.title.current", nil); cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - cell.detailTextLabel.text = getPreference(@"default_gamepad_ctrl"); + cell.detailTextLabel.text = getPrefObject(@"control.default_gamepad_ctrl"); } else if(indexPath.section == 1 || indexPath.section == 2) { NSNumber *keycode = (NSNumber *)item[@"keycode"]; - cell.textLabel.text = localize(([NSString stringWithFormat:@"controller_configurator.%@.title.%@", getPreference(@"controller_type"), item[@"name"]]), nil); + cell.textLabel.text = localize(([NSString stringWithFormat:@"controller_configurator.%@.title.%@", getPrefObject(@"control.controller_type"), item[@"name"]]), nil); UITextField *view = (id)cell.accessoryView; if (view == nil) { view = [[PickTextField alloc] initWithFrame:CGRectMake(0, 0, cell.bounds.size.width / 2.1, cell.bounds.size.height)]; @@ -145,7 +145,7 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); objc_setAssociatedObject(view, @"item", item, OBJC_ASSOCIATION_ASSIGN); } else if(indexPath.section == 3) { cell.textLabel.text = localize([NSString stringWithFormat:@"controller_configurator.title.type.%@", item[@"name"]], nil); - if ([getPreference(@"controller_type") isEqualToString:item[@"name"]]) { + if ([getPrefObject(@"control.controller_type") isEqualToString:item[@"name"]]) { cell.accessoryType = UITableViewCellAccessoryCheckmark; } else { cell.accessoryType = UITableViewCellAccessoryNone; @@ -172,7 +172,7 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); if(indexPath.section == 0) { [self actionMenuLoad]; } else if(indexPath.section == 3) { - setPreference(@"controller_type", self.prefControllerTypes[indexPath.row][@"name"]); + setPrefObject(@"control.controller_type", self.prefControllerTypes[indexPath.row][@"name"]); NSIndexSet *section = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 3)]; [self.tableView reloadSections:section withRowAnimation:UITableViewRowAnimationNone]; } @@ -255,9 +255,9 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); - (void)actionMenuLoad { [self actionOpenFilePicker:^void(NSString* name) { - NSString *currentFile = [NSString stringWithFormat:@"%@.json", getPreference(@"default_gamepad_ctrl")]; + NSString *currentFile = [NSString stringWithFormat:@"%@.json", getPrefObject(@"control.default_gamepad_ctrl")]; if(![currentFile isEqualToString:name]) { - setPreference(@"default_gamepad_ctrl", [NSString stringWithFormat:@"%@.json", name]); + setPrefObject(@"control.default_gamepad_ctrl", [NSString stringWithFormat:@"%@.json", name]); [self loadGamepadConfigurationFile]; [self.tableView reloadData]; } @@ -293,7 +293,7 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); [self dismissModalViewController]; } - setPreference(@"default_gamepad_ctrl", [NSString stringWithFormat:@"%@.json", field.text]); + setPrefObject(@"control.default_gamepad_ctrl", [NSString stringWithFormat:@"%@.json", field.text]); }]]; if (exit) { [controller addAction:[UIAlertAction actionWithTitle:localize(@"custom_controls.control_menu.discard_changes", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) { @@ -309,7 +309,7 @@ typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); } - (void)exitButtonSelector { - NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/%@", getenv("POJAV_HOME"), getPreference(@"default_gamepad_ctrl")]; + NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/%@", getenv("POJAV_HOME"), getPrefObject(@"control.default_gamepad_ctrl")]; if([self.currentMappings isEqualToDictionary:parseJSONFromFile(gamepadPath)]) { [self dismissModalViewController]; } else { diff --git a/Natives/LauncherPrefGameDirViewController.m b/Natives/LauncherPrefGameDirViewController.m index dc23a435..b0979b9e 100644 --- a/Natives/LauncherPrefGameDirViewController.m +++ b/Natives/LauncherPrefGameDirViewController.m @@ -2,11 +2,10 @@ #import "LauncherPreferences.h" #import "LauncherPrefGameDirViewController.h" #import "NSFileManager+NRFileManager.h" +#import "PLProfiles.h" #import "ios_uikit_bridge.h" #import "utils.h" -extern void init_setupLauncherProfiles(); - @interface LauncherPrefGameDirViewController () @property(nonatomic) NSMutableArray *array; @end @@ -39,16 +38,16 @@ extern void init_setupLauncherProfiles(); } - (void)changeSelectionTo:(NSString *)name { - setPreference(@"game_directory", name); + if (getenv("DEMO_LOCK")) return; + + setPrefObject(@"general.game_directory", name); NSString *multidirPath = [NSString stringWithFormat:@"%s/instances/%@", getenv("POJAV_HOME"), name]; - NSString *lasmPath = [NSString stringWithFormat:@"%s/Library/Application Support/minecraft", getenv("POJAV_HOME")]; + NSString *lasmPath = @(getenv("POJAV_GAME_DIR")); [NSFileManager.defaultManager removeItemAtPath:lasmPath error:nil]; [NSFileManager.defaultManager createSymbolicLinkAtPath:lasmPath withDestinationPath:multidirPath error:nil]; [NSFileManager.defaultManager changeCurrentDirectoryPath:lasmPath]; - init_setupLauncherProfiles(); - if ([getPreference(@"selected_version_type") intValue] == 0) { - [(LauncherNavigationController *)self.navigationController reloadVersionList:0]; - } + toggleIsolatedPref(NO); + [self.navigationController performSelector:@selector(reloadProfileList) withObject:nil]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section @@ -89,7 +88,7 @@ extern void init_setupLauncherProfiles(); }); }); - if ([getPreference(@"game_directory") isEqualToString:self.array[indexPath.row]]) { + if ([getPrefObject(@"general.game_directory") isEqualToString:self.array[indexPath.row]]) { cell.accessoryType = UITableViewCellAccessoryCheckmark; } else { cell.accessoryType = UITableViewCellAccessoryNone; @@ -225,12 +224,12 @@ viewForFooterInSection:(NSInteger)section NSString *directory = [NSString stringWithFormat:@"%s/instances/%@", getenv("POJAV_HOME"), self.array[indexPath.row]]; NSError *error; if([NSFileManager.defaultManager removeItemAtPath:directory error:&error]) { - if ([getPreference(@"game_directory") isEqualToString:self.array[indexPath.row]]) { + if ([getPrefObject(@"general.game_directory") isEqualToString:self.array[indexPath.row]]) { [self changeSelectionTo:self.array[0]]; [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].accessoryType = UITableViewCellAccessoryCheckmark; } [self.array removeObjectAtIndex:indexPath.row]; - [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; + [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } else { showDialog(localize(@"Error", nil), error.localizedDescription); } diff --git a/Natives/LauncherPrefManageJREViewController.m b/Natives/LauncherPrefManageJREViewController.m index c6befa71..6e1e843b 100644 --- a/Natives/LauncherPrefManageJREViewController.m +++ b/Natives/LauncherPrefManageJREViewController.m @@ -67,7 +67,7 @@ static WFWorkflowProgressView* currentProgressView; self.sortedJavaVersions = @[@(DEFAULT_JRE)].mutableCopy; self.selectedRTTags = @[@"1_16_5_older", @"1_17_newer", @"install_jar"]; - self.selectedRuntimes = getPreference(@"java_homes"); + self.selectedRuntimes = getPrefObject(@"java.java_homes"); NSString *internalPath = [NSString stringWithFormat:@"%s/java_runtimes", getenv("BUNDLE_PATH")]; NSString *externalPath = [NSString stringWithFormat:@"%s/java_runtimes", getenv("POJAV_HOME")]; @@ -269,7 +269,7 @@ static WFWorkflowProgressView* currentProgressView; } else { self.selectedRuntimes[version.stringValue] = name; } - setPreference(@"java_homes", self.selectedRuntimes); + setPrefObject(@"java.java_homes", self.selectedRuntimes); // Update checkmark NSInteger runtimeCount = [self tableView:tableView numberOfRowsInSection:indexPath.section]; @@ -303,7 +303,7 @@ static WFWorkflowProgressView* currentProgressView; handler:^(UIAction *action) { cell.detailTextLabel.text = version; ((NSMutableDictionary *)self.selectedRuntimes[@"0"])[self.selectedRTTags[indexPath.row]] = self.sortedJavaVersions[i].stringValue; - setPreference(@"java_homes", self.selectedRuntimes); + setPrefObject(@"java.java_homes", self.selectedRuntimes); }]]; } @@ -439,7 +439,7 @@ styleForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration self.selectedRuntimes[version.stringValue] = self.javaRuntimes[version][0]; } - setPreference(@"java_homes", self.selectedRuntimes); + setPrefObject(@"java.java_homes", self.selectedRuntimes); } - (void)listJREInPath:(NSString *)path markInternal:(BOOL)markInternal { diff --git a/Natives/LauncherPreferences.h b/Natives/LauncherPreferences.h index dac3ef68..46e99289 100644 --- a/Natives/LauncherPreferences.h +++ b/Natives/LauncherPreferences.h @@ -1,10 +1,30 @@ #import void loadPreferences(BOOL reset); -id getPreference(NSString* key); -NSMutableDictionary* getDictionary(NSString *type); -void setDefaultValueForPref(NSMutableDictionary *dict, NSString* key, id value); -void setPreference(NSString* key, id value); +void toggleIsolatedPref(BOOL forceEnable); + +id getPrefObject(NSString *key); +static inline BOOL getPrefBool(NSString *key) { + return [getPrefObject(key) boolValue]; +} +static inline float getPrefFloat(NSString *key) { + return [getPrefObject(key) floatValue]; +} +static inline NSInteger getPrefInt(NSString *key) { + return [getPrefObject(key) intValue]; +} + +void setPrefObject(NSString *key, id value); +static inline void setPrefBool(NSString *key, BOOL value) { + setPrefObject(key, @(value)); +} +static inline void setPrefFloat(NSString *key, float value) { + setPrefObject(key, @(value)); +} +static inline void setPrefInt(NSString *key, NSInteger value) { + setPrefObject(key, @(value)); +} + void resetWarnings(); BOOL getEntitlementValue(NSString *key); @@ -14,3 +34,6 @@ CGRect getSafeArea(); void setSafeArea(CGRect safeArea); NSString* getSelectedJavaHome(NSString* defaultJRETag, int minVersion); + +NSArray* getRendererKeys(BOOL containsDefault); +NSArray* getRendererNames(BOOL containsDefault); diff --git a/Natives/LauncherPreferences.m b/Natives/LauncherPreferences.m index 42e76303..6135e84d 100644 --- a/Natives/LauncherPreferences.m +++ b/Natives/LauncherPreferences.m @@ -1,181 +1,47 @@ #import "config.h" +#import "utils.h" #import "LauncherPreferences.h" +#import "PLPreferences.h" #import "UIKit+hook.h" #import -NSMutableDictionary *prefDict; -NSString* prefPath; -// developer debug dict -NSMutableDictionary *debugPrefDict; -// environment variables dict -NSMutableDictionary *envPrefDict; -// warnings dict -NSMutableDictionary *warnPrefDict; - -id getPreference(NSString* key) { - 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]; - } else if (!(prefDict[key] == [NSNull null] || prefDict[key] == nil) || [key hasPrefix:@"internal_"]) { - return prefDict[key]; - } else { - NSLog(@"LauncherPreferences: Unknown key %@", key); - } - - NSLog(@"[Pre-init] LauncherPreferences: %@ is NULL", key); - return nil; -} - -NSMutableDictionary *getDictionary(NSString *type) { - if([type containsString:@"env"]) { - return envPrefDict; - } else if([type containsString:@"warn"]) { - return warnPrefDict; - } else if([type containsString:@"base"]) { - return prefDict; - } - - return nil; -} - -void setDefaultValueForPref(NSMutableDictionary *dict, NSString* key, id value) { - if (!dict[key]) { - dict[key] = value; - NSLog(@"[Pre-init] LauncherPreferences: Set default %@: %@", key, value); - } -} - -void setPreference(NSString* key, id value) { - 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)) { - warnPrefDict[key] = value; - prefDict[@"warnings"] = warnPrefDict; - } else if (!(prefDict[key] == [NSNull null] || prefDict[key] == nil) || [key hasPrefix:@"internal_"]) { - prefDict[key] = value; - } else { - NSLog(@"LauncherPreferences: Unknown key %@", key); - } - [prefDict writeToFile:prefPath atomically:YES]; -} - -void fillDefaultWarningDict() { - setDefaultValueForPref(warnPrefDict, @"local_warn", @YES); - setDefaultValueForPref(warnPrefDict, @"mem_warn", @YES); - setDefaultValueForPref(warnPrefDict, @"auto_ram_warn", @YES); - setDefaultValueForPref(warnPrefDict, @"limited_ram_warn", @YES); -} - -NSMutableDictionary* getDictionarySafe(NSString* key) { - if (prefDict[key] == nil) { - return [[NSMutableDictionary alloc] init]; - } - return prefDict[key]; -} +static PLPreferences* pref; void loadPreferences(BOOL reset) { assert(getenv("POJAV_HOME")); - prefPath = [@(getenv("POJAV_HOME")) - stringByAppendingPathComponent:@"launcher_preferences.plist"]; - - NSFileManager *fileManager = [NSFileManager defaultManager]; - prefDict = [NSMutableDictionary dictionaryWithContentsOfFile:prefPath]; if (reset) { - [prefDict removeAllObjects]; - } else if (![fileManager fileExistsAtPath:prefPath]) { - prefDict = [[NSMutableDictionary alloc] init]; + [pref reset]; + } else { + pref = [[PLPreferences alloc] initWithAutomaticMigrator]; + pref.instancePath = [NSString stringWithFormat:@"%s/launcher_preferences.plist", getenv("POJAV_GAME_DIR")]; + [pref toggleIsolationForced:NO]; } - debugPrefDict = getDictionarySafe(@"debugs"); - envPrefDict = getDictionarySafe(@"env_vars"); - warnPrefDict = getDictionarySafe(@"warnings"); +} - setDefaultValueForPref(envPrefDict, @"resolution", @(100)); - setDefaultValueForPref(envPrefDict, @"gesture_mouse", @YES); - setDefaultValueForPref(envPrefDict, @"gesture_hotbar", @YES); - setDefaultValueForPref(envPrefDict, @"press_duration", @(400)); - setDefaultValueForPref(envPrefDict, @"mouse_scale", @(100)); - setDefaultValueForPref(envPrefDict, @"mouse_speed", @(100)); - setDefaultValueForPref(envPrefDict, @"gyroscope_enable", @NO); - setDefaultValueForPref(envPrefDict, @"gyroscope_invert_x_axis", @NO); - setDefaultValueForPref(envPrefDict, @"gyroscope_sensitivity", @(100)); - setDefaultValueForPref(envPrefDict, @"default_ctrl", @"default.json"); - setDefaultValueForPref(envPrefDict, @"default_gamepad_ctrl", @"default.json"); - setDefaultValueForPref(envPrefDict, @"game_directory", @"default"); - setDefaultValueForPref(envPrefDict, @"java_args", @""); - setDefaultValueForPref(envPrefDict, @"env_variables", @""); - setDefaultValueForPref(envPrefDict, @"allocated_memory", [NSNumber numberWithFloat:roundf((NSProcessInfo.processInfo.physicalMemory / 1048576) * 0.25)]); - setDefaultValueForPref(envPrefDict, @"jitstreamer_server", @"69.69.0.1"); - setDefaultValueForPref(envPrefDict, @"max_framerate", @YES); - setDefaultValueForPref(envPrefDict, @"renderer", @"auto"); - setDefaultValueForPref(envPrefDict, @"fullscreen_airplay", @YES); - setDefaultValueForPref(envPrefDict, @"hardware_hide", @NO); - setDefaultValueForPref(envPrefDict, @"silence_other_audio", @NO); - setDefaultValueForPref(envPrefDict, @"silence_with_switch", @NO); - setDefaultValueForPref(envPrefDict, @"disable_haptics", @NO); - setDefaultValueForPref(envPrefDict, @"java_homes", @{ - @"0": @{ - @"1_16_5_older": @"8", - @"1_17_newer": @"17", - @"install_jar": @"8" - }.mutableCopy, - @"8": @"internal", - @"17": @"internal", - }.mutableCopy); - // stub pref - setDefaultValueForPref(prefDict, @"manage_runtime", @""); - - setDefaultValueForPref(prefDict, @"button_scale", @(100)); - setDefaultValueForPref(prefDict, @"selected_account", @""); - setDefaultValueForPref(prefDict, @"selected_version", @"1.7.10"); - setDefaultValueForPref(prefDict, @"selected_version_type", @(0)); - setDefaultValueForPref(prefDict, @"appicon", @"AppIcon-Light"); - setDefaultValueForPref(prefDict, @"debug_logging", @(CONFIG_RELEASE != 1)); - setDefaultValueForPref(prefDict, @"cosmetica", @YES); - setDefaultValueForPref(prefDict, @"controller_type", @"xbox"); - setDefaultValueForPref(prefDict, @"slimmed", @NO); - setDefaultValueForPref(prefDict, @"slideable_hotbar", @NO); - setDefaultValueForPref(prefDict, @"virtmouse_enable", @NO); - setDefaultValueForPref(prefDict, @"check_sha", @YES); - setDefaultValueForPref(prefDict, @"auto_ram", @(!getEntitlementValue(@"com.apple.private.memorystatus"))); - setDefaultValueForPref(prefDict, @"legacy_version_counter", @(0)); - setDefaultValueForPref(prefDict, @"hidden_sidebar", @(realUIIdiom == UIUserInterfaceIdiomPhone)); - setDefaultValueForPref(prefDict, @"enable_altkit", @YES); - - setDefaultValueForPref(debugPrefDict, @"debug_skip_wait_jit", @NO); - setDefaultValueForPref(debugPrefDict, @"debug_hide_home_indicator", @NO); - setDefaultValueForPref(debugPrefDict, @"debug_ipad_ui", @(realUIIdiom == UIUserInterfaceIdiomPad)); - setDefaultValueForPref(debugPrefDict, @"debug_auto_correction", @YES); - setDefaultValueForPref(debugPrefDict, @"debug_show_layout_bounds", @NO); - setDefaultValueForPref(debugPrefDict, @"debug_show_layout_overlap", @NO); - - fillDefaultWarningDict(); +void toggleIsolatedPref(BOOL forceEnable) { + [pref toggleIsolationForced:forceEnable]; +} - prefDict[@"debugs"] = debugPrefDict; - prefDict[@"env_vars"] = envPrefDict; - prefDict[@"warnings"] = warnPrefDict; +id getPrefObject(NSString *key) { + return [pref getObject:key]; +} - [prefDict writeToFile:prefPath atomically:YES]; +void setPrefObject(NSString *key, id value) { + [pref setObject:key value:value]; } void resetWarnings() { - [warnPrefDict removeAllObjects]; - fillDefaultWarningDict(); - prefDict[@"warnings"] = warnPrefDict; - [prefDict writeToFile:prefPath atomically:YES]; + for (int i = 0; i < pref.globalPref[@"warnings"].count; i++) { + NSString *key = pref.globalPref[@"warnings"].allKeys[i]; + pref.globalPref[@"warnings"][key] = @YES; + } } #pragma mark Safe area CGRect getSafeArea() { CGRect screenBounds = UIScreen.mainScreen.bounds; - UIEdgeInsets safeArea = UIEdgeInsetsFromString(getPreference(@"control_safe_area")); + UIEdgeInsets safeArea = UIEdgeInsetsFromString(getPrefObject(@"control.control_safe_area")); if (screenBounds.size.width < screenBounds.size.height) { safeArea = UIEdgeInsetsMake(safeArea.right, safeArea.top, safeArea.left, safeArea.bottom); } @@ -199,7 +65,7 @@ void setSafeArea(CGRect frame) { screenSize.height - CGRectGetMaxY(frame), screenSize.width - CGRectGetMaxX(frame)); } - setPreference(@"control_safe_area", NSStringFromUIEdgeInsets(safeArea)); + setPrefObject(@"control.control_safe_area", NSStringFromUIEdgeInsets(safeArea)); } UIEdgeInsets getDefaultSafeArea() { @@ -216,7 +82,7 @@ UIEdgeInsets getDefaultSafeArea() { #pragma mark Java runtime NSString* getSelectedJavaHome(NSString* defaultJRETag, int minVersion) { - NSDictionary *pref = getPreference(@"java_homes"); + NSDictionary *pref = getPrefObject(@"java.java_homes"); NSDictionary *selected = pref[@"0"]; NSString *selectedVer = selected[defaultJRETag]; if ([defaultJRETag isEqualToString:@"install_jar"] && minVersion > selectedVer.intValue) { @@ -248,3 +114,57 @@ NSString* getSelectedJavaHome(NSString* defaultJRETag, int minVersion) { return nil; } } + +#pragma mark Renderer +NSArray* getRendererKeys(BOOL containsDefault) { + NSMutableArray *array = @[ + @"auto", + @ RENDERER_NAME_GL4ES, + @ RENDERER_NAME_MTL_ANGLE + ].mutableCopy; + + if (containsDefault) { + [array insertObject:@"(default)" atIndex:0]; + } + +#if CONFIG_RELEASE + if(@available(iOS 16.0, *)) { + // Disabling Zink on iOS 16.0+ to figure out what's wrong with it + } else { +#endif + [array addObject:@ RENDERER_NAME_VK_ZINK]; +#if CONFIG_RELEASE + } +#endif + return array; +} + +NSArray* getRendererNames(BOOL containsDefault) { + NSMutableArray *array; + +#if CONFIG_RELEASE + if(@available(iOS 16.0, *)) { + // Disabling Zink on iOS 16.0+ to figure out what's wrong with it + array = @[ + localize(@"preference.title.renderer.release.auto", nil), + localize(@"preference.title.renderer.release.gl4es", nil), + localize(@"preference.title.renderer.release.angle", nil) + ].mutableCopy; + } else { +#endif + array = @[ + localize(@"preference.title.renderer.debug.auto", nil), + localize(@"preference.title.renderer.debug.gl4es", nil), + localize(@"preference.title.renderer.debug.angle", nil), + localize(@"preference.title.renderer.debug.zink", nil) + ].mutableCopy; +#if CONFIG_RELEASE + } +#endif + + if (containsDefault) { + [array insertObject:@"(default)" atIndex:0]; + } + + return array; +} diff --git a/Natives/LauncherPreferencesViewController.m b/Natives/LauncherPreferencesViewController.m index e998ed33..a2c0d785 100644 --- a/Natives/LauncherPreferencesViewController.m +++ b/Natives/LauncherPreferencesViewController.m @@ -6,7 +6,6 @@ #import "LauncherPreferences.h" #import "LauncherPreferencesViewController.h" #import "LauncherPrefContCfgViewController.h" -#import "LauncherPrefGameDirViewController.h" #import "LauncherPrefManageJREViewController.h" #import "UIKit+hook.h" @@ -32,13 +31,16 @@ - (void)viewDidLoad { - self.getPreference = ^id(NSString *key){ - return getPreference(key); + self.getPreference = ^id(NSString *section, NSString *key){ + NSString *keyFull = [NSString stringWithFormat:@"%@.%@", section, key]; + return getPrefObject(keyFull); }; - self.setPreference = ^(NSString *key, id value){ - setPreference(key, value); + self.setPreference = ^(NSString *section, NSString *key, id value){ + NSString *keyFull = [NSString stringWithFormat:@"%@.%@", section, key]; + setPrefObject(keyFull, value); }; + self.hasDetail = YES; self.prefDetailVisible = self.navigationController == nil; if (self.navigationController == nil) { self.tableView.alpha = 0.9; @@ -46,36 +48,8 @@ self.prefSections = @[@"general", @"video", @"control", @"java", @"debug"]; -#if CONFIG_RELEASE - if(@available(iOS 16.0, *)) { - // Disabling Zink on iOS 16.0+ to figure out what's wrong with it - self.rendererKeys = @[ - @"auto", - @ RENDERER_NAME_GL4ES, - @ RENDERER_NAME_MTL_ANGLE - ]; - self.rendererList = @[ - localize(@"preference.title.renderer.release.auto", nil), - localize(@"preference.title.renderer.release.gl4es", nil), - localize(@"preference.title.renderer.release.angle", nil) - ]; - } else { -#endif - self.rendererKeys = @[ - @"auto", - @ RENDERER_NAME_GL4ES, - @ RENDERER_NAME_MTL_ANGLE, - @ RENDERER_NAME_VK_ZINK - ]; - self.rendererList = @[ - localize(@"preference.title.renderer.debug.auto", nil), - localize(@"preference.title.renderer.debug.gl4es", nil), - localize(@"preference.title.renderer.debug.angle", nil), - localize(@"preference.title.renderer.debug.zink", nil) - ]; -#if CONFIG_RELEASE - } -#endif + self.rendererKeys = getRendererKeys(NO); + self.rendererList = getRendererNames(NO); BOOL(^whenNotInGame)() = ^BOOL(){ return self.navigationController != nil; @@ -84,15 +58,6 @@ @[ // General settings @{@"icon": @"cube"}, - @{@"key": @"game_directory", - @"icon": @"folder", - @"type": self.typeChildPane, - @"enableCondition": ^BOOL(){ - return whenNotInGame() && !getenv("DEMO_LOCK"); - }, - @"canDismissWithSwipe": @YES, - @"class": LauncherPrefGameDirViewController.class, - }, @{@"key": @"check_sha", @"hasDetail": @YES, @"icon": @"lock.shield", @@ -196,8 +161,9 @@ if([NSFileManager.defaultManager removeItemAtPath:demoPath error:&error]) { [NSFileManager.defaultManager createDirectoryAtPath:demoPath withIntermediateDirectories:YES attributes:nil error:nil]; - if ([self.getPreference(@"selected_version_type") intValue] == 0 && getenv("DEMO_LOCK")) { - [(LauncherNavigationController *)self.navigationController reloadVersionList:0]; + [NSFileManager.defaultManager changeCurrentDirectoryPath:demoPath]; + if (getenv("DEMO_LOCK")) { + [(LauncherNavigationController *)self.navigationController fetchLocalVersionList]; } } else { NSLog(@"Error in erase_demo_data: %@", error); @@ -390,7 +356,7 @@ @"min": @(250), @"max": @((NSProcessInfo.processInfo.physicalMemory / 1048576) * 0.85), @"enableCondition": ^BOOL(){ - return ![self.getPreference(@"auto_ram") boolValue] && whenNotInGame(); + return ![self.getPreference(@"java", @"auto_ram") boolValue] && whenNotInGame(); }, @"warnCondition": ^BOOL(DBNumberedSlider *view){ return view.value >= NSProcessInfo.processInfo.physicalMemory / 1048576 * 0.37; @@ -442,7 +408,7 @@ @"icon": @"square.on.square", @"type": self.typeSwitch, @"enableCondition": ^BOOL(){ - return [self.getPreference(@"debug_show_layout_bounds") boolValue] && whenNotInGame(); + return [self.getPreference(@"debug", @"debug_show_layout_bounds") boolValue] && whenNotInGame(); } } ] diff --git a/Natives/LauncherProfileEditorViewController.h b/Natives/LauncherProfileEditorViewController.h new file mode 100644 index 00000000..829368b9 --- /dev/null +++ b/Natives/LauncherProfileEditorViewController.h @@ -0,0 +1,6 @@ +#import +#import "PLPrefTableViewController.h" + +@interface LauncherProfileEditorViewController : PLPrefTableViewController +@property(nonatomic) NSMutableDictionary* profile; +@end diff --git a/Natives/LauncherProfileEditorViewController.m b/Natives/LauncherProfileEditorViewController.m new file mode 100644 index 00000000..68b2b5d4 --- /dev/null +++ b/Natives/LauncherProfileEditorViewController.m @@ -0,0 +1,304 @@ +#import "LauncherNavigationController.h" +#import "LauncherPreferences.h" +#import "LauncherProfileEditorViewController.h" +#import "MinecraftResourceUtils.h" +#import "PLProfiles.h" +#import "ios_uikit_bridge.h" +#import "utils.h" + +@interface LauncherProfileEditorViewController() +@property(nonatomic) NSString* oldName; + +@property(nonatomic) NSArray *versionList; +@property(nonatomic) UITextField* versionTextField; +@property(nonatomic) UISegmentedControl* versionTypeControl; +@property(nonatomic) UIPickerView* versionPickerView; +@property(nonatomic) UIToolbar* versionPickerToolbar; +@property(nonatomic) int versionSelectedAt; +@end + +@implementation LauncherProfileEditorViewController + +- (void)viewDidLoad { + // Setup navigation bar + self.title = localize(@"Edit profile", nil); + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(actionDone)]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemClose target:self action:@selector(actionClose)]; + + // Setup appearance + self.prefSectionsVisible = YES; + + // Setup preference getter and setter + __weak LauncherProfileEditorViewController *weakSelf = self; + self.getPreference = ^id(NSString *section, NSString *key){ + NSString *value = weakSelf.profile[key]; + if (value.length > 0 || ![weakSelf isPickFieldAtSection:section key:key]) { + return value; + } else { + return @"(default)"; + } + }; + self.setPreference = ^(NSString *section, NSString *key, NSString *value){ + if ([value isEqualToString:@"(default)"] && [weakSelf isPickFieldAtSection:section key:key]) { + [weakSelf.profile removeObjectForKey:key]; + } else { + weakSelf.profile[key] = value; + } + }; + + // Obtain all the lists + self.oldName = self.getPreference(nil, @"name"); + if ([self.oldName length] == 0) { + self.setPreference(nil, @"name", @"New Profile"); + } + NSArray *rendererKeys = getRendererKeys(YES); + NSArray *rendererList = getRendererNames(YES); + NSArray *touchControlList = [self listFilesAtPath:[NSString stringWithFormat:@"%s/controlmap", getenv("POJAV_HOME")]]; + NSArray *gamepadControlList = [self listFilesAtPath:[NSString stringWithFormat:@"%s/controlmap/gamepads", getenv("POJAV_HOME")]]; + NSMutableArray *javaList = [getPrefObject(@"java.java_homes") allKeys].mutableCopy; + [javaList sortUsingSelector:@selector(compare:)]; + javaList[0] = @"(default)"; + + // Setup version picker + [self setupVersionPicker]; + id typeVersionPicker = ^void(UITableViewCell *cell, NSString *section, NSString *key, NSDictionary *item){ + self.typeTextField(cell, section, key, item); + UITextField *textField = (id)cell.accessoryView; + weakSelf.versionTextField = textField; + textField.inputAccessoryView = weakSelf.versionPickerToolbar; + textField.inputView = weakSelf.versionPickerView; + // Auto pick version type + if (self.versionList) return; + if ([MinecraftResourceUtils findVersion:textField.text inList:localVersionList]) { + self.versionTypeControl.selectedSegmentIndex = 0; + } else { + NSDictionary *selected = (id)[MinecraftResourceUtils findVersion:textField.text inList:remoteVersionList]; + if (selected) { + NSArray *types = @[@"installed", @"release", @"snapshot", @"old_beta", @"old_alpha"]; + NSString *type = selected[@"type"]; + self.versionTypeControl.selectedSegmentIndex = [types indexOfObject:type]; + } else { + // Version not found + self.versionTypeControl.selectedSegmentIndex = 0; + } + } + self.versionSelectedAt = -1; + [self changeVersionType:nil]; + }; + + self.prefContents = @[ + @[ + // General settings + @{@"key": @"name", + @"icon": @"tag", + @"title": @"preference.profile.title.name", + @"type": self.typeTextField, + @"placeholder": self.oldName + }, + @{@"key": @"lastVersionId", + @"icon": @"archivebox", + @"title": @"preference.profile.title.version", + @"type": typeVersionPicker, + @"placeholder": self.getPreference(nil, @"lastVersionId") + }, + @{@"key": @"gameDir", + @"icon": @"folder", + @"title": @"preference.title.game_directory", + @"type": self.typeTextField, + @"placeholder": [NSString stringWithFormat:@". -> /Documents/instances/%@", getPrefObject(@"general.game_directory")] + }, + // Video and renderer settings + @{@"key": @"renderer", + @"icon": @"cpu", + @"type": self.typePickField, + @"pickKeys": rendererKeys, + @"pickList": rendererList + }, + // Control settings + @{@"key": @"defaultTouchCtrl", + @"icon": @"hand.tap", + @"title": @"preference.profile.title.default_touch_control", + @"type": self.typePickField, + @"pickKeys": touchControlList, + @"pickList": touchControlList + }, + @{@"key": @"defaultGamepadCtrl", + @"icon": @"gamecontroller", + @"title": @"preference.profile.title.default_gamepad_control", + @"type": self.typePickField, + @"pickKeys": gamepadControlList, + @"pickList": gamepadControlList + }, + // Java tweaks + @{@"key": @"javaVersion", + @"icon": @"cube", + @"title": @"preference.manage_runtime.header.default", + @"type": self.typePickField, + @"pickKeys": javaList, + @"pickList": javaList + }, + @{@"key": @"javaArgs", + @"icon": @"slider.vertical.3", + @"title": @"preference.title.java_args", + @"type": self.typeTextField, + @"placeholder": @"(default)" + } + ] + ]; + + [super viewDidLoad]; +} + +- (void)actionClose { + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)actionDone { + // We might be saving without ending editing, so make sure textFieldDidEndEditing is always called + UITextField *currentTextField = [self performSelector:@selector(_firstResponder)]; + if ([currentTextField isKindOfClass:UITextField.class]) { + [self textFieldDidEndEditing:currentTextField]; + } + + if ([self.profile[@"name"] length] == 0 && self.oldName.length > 0) { + // Return to its old name + self.profile[@"name"] = self.oldName; + } + + if ([self.oldName isEqualToString:self.profile[@"name"]]) { + // Not a rename, directly create/replace + PLProfiles.current.profiles[self.oldName] = self.profile; + } else if (!PLProfiles.current.profiles[self.profile[@"name"]]) { + // A rename, remove then re-add to update its key name + if (self.oldName.length > 0) { + [PLProfiles.current.profiles removeObjectForKey:self.oldName]; + } + PLProfiles.current.profiles[self.profile[@"name"]] = self.profile; + // Update selected name + if ([PLProfiles.current.selectedProfileName isEqualToString:self.oldName]) { + PLProfiles.current.selectedProfileName = self.profile[@"name"]; + } + } else { + // Cancel rename since a profile with the same name already exists + showDialog(localize(@"Error", nil), localize(@"profile.error.name_exists", nil)); + // Skip dismissing this view controller + return; + } + + [PLProfiles.current save]; + [self actionClose]; + + // Call LauncherProfilesViewController's viewWillAppear + UINavigationController *navVC = (id) ((UISplitViewController *)self.presentingViewController).viewControllers[1]; + [navVC.viewControllers[0] viewWillAppear:NO]; +} + +- (BOOL)isPickFieldAtSection:(NSString *)section key:(NSString *)key { + NSDictionary *pref = [self.prefContents[0] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(key == %@)", key]].firstObject; + return pref[@"type"] == self.typePickField; +} + +- (NSArray *)listFilesAtPath:(NSString *)path { + NSMutableArray *files = [NSFileManager.defaultManager contentsOfDirectoryAtPath:path error:nil].mutableCopy; + for (int i = 0; i < files.count;) { + if ([files[i] hasSuffix:@".json"]) { + i++; + } else { + [files removeObjectAtIndex:i]; + } + } + [files insertObject:@"(default)" atIndex:0]; + return files; +} + +#pragma mark Version picker + +- (void)setupVersionPicker { + self.versionPickerView = [[UIPickerView alloc] init]; + self.versionPickerView.delegate = self; + self.versionPickerView.dataSource = self; + self.versionPickerToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, 44.0)]; + self.versionTypeControl = [[UISegmentedControl alloc] initWithItems:@[ + localize(@"Installed", nil), + localize(@"Releases", nil), + localize(@"Snapshot", nil), + localize(@"Old-beta", nil), + localize(@"Old-alpha", nil) + ]]; + [self.versionTypeControl addTarget:self action:@selector(changeVersionType:) forControlEvents:UIControlEventValueChanged]; + self.versionPickerToolbar.items = @[ + [[UIBarButtonItem alloc] initWithCustomView:self.versionTypeControl], + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil], + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(versionClosePicker)] + ]; +} + +- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { + if (self.versionList.count == 0) { + self.versionTextField.text = @""; + return; + } + self.versionSelectedAt = row; + self.versionTextField.text = [self pickerView:pickerView titleForRow:row forComponent:component]; +} + +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView { + return 1; +} + +- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { + return self.versionList.count; +} + +- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { + if (self.versionList.count <= row) return nil; + NSObject *object = self.versionList[row]; + if ([object isKindOfClass:[NSString class]]) { + return (NSString*) object; + } else { + return [object valueForKey:@"id"]; + } +} + +- (void)versionClosePicker { + [self.versionTextField endEditing:YES]; + [self pickerView:self.versionPickerView didSelectRow:[self.versionPickerView selectedRowInComponent:0] inComponent:0]; +} + +- (void)changeVersionType:(UISegmentedControl *)sender { + if (sender || !self.versionList) { + if (self.versionTypeControl.selectedSegmentIndex == 0) { + // installed + self.versionList = localVersionList; + } else { + NSString *type = @[@"installed", @"release", @"snapshot", @"old_beta", @"old_alpha"][self.versionTypeControl.selectedSegmentIndex]; + self.versionList = [remoteVersionList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(type == %@)", type]]; + } + [self.versionPickerView reloadAllComponents]; + } + + if (self.versionSelectedAt == -1) { + NSDictionary *selected = (id)[MinecraftResourceUtils findVersion:self.versionTextField.text inList:self.versionList]; + self.versionSelectedAt = [self.versionList indexOfObject:selected]; + } else { + // Find the most matching version for this type + NSObject *lastSelected = nil; + if (self.versionList.count > 0 && self.versionSelectedAt >= 0) { + lastSelected = self.versionList[self.versionSelectedAt]; + } + if (self.versionSelectedAt < 0 && lastSelected != nil) { + NSObject *nearest = [MinecraftResourceUtils findNearestVersion:lastSelected expectedType:self.versionTypeControl.selectedSegmentIndex]; + if (nearest != nil) { + self.versionSelectedAt = [self.versionList indexOfObject:(id)nearest]; + } + } + lastSelected = nil; + // Get back the currently selected in case none matching version found + self.versionSelectedAt = MIN(abs(self.versionSelectedAt), self.versionList.count - 1); + } + + [self.versionPickerView selectRow:self.versionSelectedAt inComponent:0 animated:NO]; + [self pickerView:self.versionPickerView didSelectRow:self.versionSelectedAt inComponent:0]; +} + +@end diff --git a/Natives/LauncherProfilesViewController.h b/Natives/LauncherProfilesViewController.h new file mode 100644 index 00000000..267ee289 --- /dev/null +++ b/Natives/LauncherProfilesViewController.h @@ -0,0 +1,6 @@ +#import + +@interface LauncherProfilesViewController : UITableViewController + +@end + diff --git a/Natives/LauncherProfilesViewController.m b/Natives/LauncherProfilesViewController.m new file mode 100644 index 00000000..95c380dc --- /dev/null +++ b/Natives/LauncherProfilesViewController.m @@ -0,0 +1,222 @@ +#import "LauncherMenuViewController.h" +#import "LauncherNavigationController.h" +#import "LauncherPreferences.h" +#import "LauncherPrefGameDirViewController.h" +#import "LauncherPrefManageJREViewController.h" +#import "LauncherProfileEditorViewController.h" +#import "LauncherProfilesViewController.h" +//#import "NSFileManager+NRFileManager.h" +#import "PLProfiles.h" +#import "UIKit+hook.h" +#import "ios_uikit_bridge.h" +#import "utils.h" + +@interface LauncherProfilesViewController () // + +@property(nonatomic) UIBarButtonItem *createButtonItem; +@end + +@implementation LauncherProfilesViewController + +- (id)init { + self = [super init]; + self.title = localize(@"Profiles", nil); + return self; +} + +- (NSString *)imageName { + return @"MenuProfiles"; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + id createHandler = ^(UIAction *action) { + [self actionEditProfile:@{ + @"name": @"", + @"lastVersionId": @"latest-release", + }]; + }; + UIMenu *createMenu = [UIMenu menuWithTitle:localize(@"profile.title.create", nil) image:nil identifier:nil + options:UIMenuOptionsDisplayInline + children:@[ + [UIAction + actionWithTitle:@"Vanilla" image:nil + identifier:@"vanilla" handler:createHandler], +#if 0 // TODO + [UIAction + actionWithTitle:@"OptiFine" image:nil + identifier:@"optifine" handler:createHandler], + [UIAction + actionWithTitle:@"Fabric" image:nil + identifier:@"fabric" handler:createHandler], + [UIAction + actionWithTitle:@"Forge" image:nil + identifier:@"forge" handler:createHandler] +#endif + ]]; + self.createButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd menu:createMenu]; + + self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped]; + self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + // Put navigation buttons back in place + self.navigationItem.rightBarButtonItems = @[[sidebarViewController drawAccountButton], self.createButtonItem]; + + // Pickup changes made in the profile editor and switching instance + [PLProfiles updateCurrent]; + [self.tableView reloadData]; + [self.navigationController performSelector:@selector(reloadProfileList) withObject:nil]; +} + +- (void)actionTogglePrefIsolation:(UISwitch *)sender { + if (!sender.isOn) { + setPrefBool(@"internal.isolated", NO); + } + toggleIsolatedPref(sender.isOn); +} + +- (void)actionEditProfile:(NSDictionary *)profile { + LauncherProfileEditorViewController *vc = [LauncherProfileEditorViewController new]; + vc.profile = profile.mutableCopy; + UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; + nav.navigationBar.prefersLargeTitles = YES; + nav.modalInPresentation = YES; + [self presentViewController:nav animated:YES completion:nil]; +} + +#pragma mark Table view + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 2; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + switch (section) { + case 0: return localize(@"profile.section.instance", nil); + case 1: return localize(@"profile.section.profiles", nil); + } + return nil; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + switch (section) { + case 0: return 2; + case 1: return [PLProfiles.current.profiles count]; + } + return 0; +} + +- (void)setupInstanceCell:(UITableViewCell *) cell atRow:(NSInteger)row { + cell.userInteractionEnabled = !getenv("DEMO_LOCK"); + if (row == 0) { + cell.imageView.image = [UIImage systemImageNamed:@"folder"]; + cell.textLabel.text = localize(@"preference.title.game_directory", nil); + cell.detailTextLabel.text = getenv("DEMO_LOCK") ? @".demo" : getPrefObject(@"general.game_directory"); + } else { + NSString *imageName; + if (@available(iOS 15.0, *)) { + imageName = @"folder.badge.gearshape"; + } else { + imageName = @"folder.badge.gear"; + } + cell.imageView.image = [UIImage systemImageNamed:imageName]; + cell.textLabel.text = localize(@"profile.title.separate_preference", nil); + cell.detailTextLabel.text = localize(@"profile.detail.separate_preference", nil); + UISwitch *view = [UISwitch new]; + [view setOn:getPrefBool(@"internal.isolated") animated:NO]; + [view addTarget:self action:@selector(actionTogglePrefIsolation:) forControlEvents:UIControlEventValueChanged]; + cell.accessoryView = view; + } +} + +- (void)setupProfileCell:(UITableViewCell *) cell atRow:(NSInteger)row { + NSMutableDictionary *profile = PLProfiles.current.profiles.allValues[row]; + + cell.textLabel.text = profile[@"name"]; + cell.detailTextLabel.text = profile[@"lastVersionId"]; + cell.imageView.layer.magnificationFilter = kCAFilterNearest; + + if (profile[@"icon"]) { + NSString *iconStr = [profile[@"icon"] stringByReplacingOccurrencesOfString:@"data:image/png;base64," withString:@""]; + NSData *iconData = [[NSData alloc] initWithBase64EncodedString:iconStr options:NSDataBase64DecodingIgnoreUnknownCharacters]; + cell.imageView.image = [[UIImage imageWithData:iconData] _imageWithSize:CGSizeMake(40, 40)]; + } else { + cell.imageView.image = [[UIImage imageNamed:@"DefaultProfile"] _imageWithSize:CGSizeMake(40, 40)]; + } +} + +- (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"DefaultCell"]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"DefaultCell"]; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell.detailTextLabel.numberOfLines = 0; + cell.detailTextLabel.lineBreakMode = NSLineBreakByWordWrapping; + } else { + cell.imageView.image = nil; + cell.userInteractionEnabled = YES; + cell.accessoryView = nil; + } + + if (indexPath.section == 0) { + [self setupInstanceCell:cell atRow:indexPath.row]; + } else { + [self setupProfileCell:cell atRow:indexPath.row]; + } + + cell.textLabel.enabled = cell.detailTextLabel.enabled = cell.userInteractionEnabled; + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; + + if (indexPath.section == 0) { + if (indexPath.row == 0) { + [self.navigationController pushViewController:[LauncherPrefGameDirViewController new] animated:YES]; + } + return; + } + + [self actionEditProfile:PLProfiles.current.profiles.allValues[indexPath.row]]; +} + +#pragma mark Context Menu configuration + +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (editingStyle != UITableViewCellEditingStyleDelete) return; + + UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; + NSString *title = localize(@"preference.title.confirm", nil); + // reusing the delete runtime message + NSString *message = [NSString stringWithFormat:localize(@"preference.title.confirm.delete_runtime", nil), cell.textLabel.text]; + UIAlertController *confirmAlert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleActionSheet]; + confirmAlert.popoverPresentationController.sourceView = cell; + confirmAlert.popoverPresentationController.sourceRect = cell.bounds; + UIAlertAction *ok = [UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) { + [PLProfiles.current.profiles removeObjectForKey:cell.textLabel.text]; + [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; + }]; + UIAlertAction *cancel = [UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil]; + [confirmAlert addAction:cancel]; + [confirmAlert addAction:ok]; + [self presentViewController:confirmAlert animated:YES completion:nil]; +} + +- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == 0 || PLProfiles.current.profiles.count==1) { + return UITableViewCellEditingStyleNone; + } + return UITableViewCellEditingStyleDelete; +} + +@end diff --git a/Natives/LauncherSplitViewController.m b/Natives/LauncherSplitViewController.m index 3c356e92..ced9330e 100644 --- a/Natives/LauncherSplitViewController.m +++ b/Natives/LauncherSplitViewController.m @@ -17,10 +17,8 @@ extern NSMutableDictionary *prefDict; [super viewDidLoad]; UIApplication.sharedApplication.idleTimerDisabled = YES; setViewBackgroundColor(self.view); - setDefaultValueForPref(prefDict, @"control_safe_area", NSStringFromUIEdgeInsets(getDefaultSafeArea())); - if (![getPreference(@"internal_reset_safe_area") boolValue]) { - setPreference(@"internal_reset_safe_area", @YES); - setPreference(@"control_safe_area", NSStringFromUIEdgeInsets(getDefaultSafeArea())); + if ([getPrefObject(@"control.control_safe_area") length] == 0) { + setPrefObject(@"control.control_safe_area", NSStringFromUIEdgeInsets(getDefaultSafeArea())); } self.delegate = self; @@ -52,7 +50,7 @@ extern NSMutableDictionary *prefDict; - (void)changeDisplayModeForSize:(CGSize)size { BOOL isPortrait = size.height > size.width; if (self.preferredDisplayMode == 0 || self.displayMode != UISplitViewControllerDisplayModeSecondaryOnly) { - if([getPreference(@"hidden_sidebar") boolValue] == NO) { + if(!getPrefBool(@"general.hidden_sidebar")) { self.preferredDisplayMode = isPortrait ? UISplitViewControllerDisplayModeOneOverSecondary : UISplitViewControllerDisplayModeOneBesideSecondary; diff --git a/Natives/MinecraftResourceUtils.h b/Natives/MinecraftResourceUtils.h index 8f568353..abf248da 100644 --- a/Natives/MinecraftResourceUtils.h +++ b/Natives/MinecraftResourceUtils.h @@ -11,7 +11,7 @@ typedef void (^MDCallback)(NSString *stage, NSProgress *mainProgress, NSProgress @interface MinecraftResourceUtils : NSObject + (void)downloadClientJson:(NSObject *)version progress:(NSProgress *)mainProgress callback:(MDCallback)callback success:(void (^)(NSMutableDictionary *json))success; -+ (void)downloadVersion:(NSObject *)version callback:(void (^)(NSString *stage, NSProgress *mainProgress, NSProgress *progress))callback; ++ (void)downloadVersion:(NSDictionary *)version callback:(void (^)(NSString *stage, NSProgress *mainProgress, NSProgress *progress))callback; + (void)processJVMArgs:(NSMutableDictionary *)json; diff --git a/Natives/MinecraftResourceUtils.m b/Natives/MinecraftResourceUtils.m index 6133ba60..b0388130 100644 --- a/Natives/MinecraftResourceUtils.m +++ b/Natives/MinecraftResourceUtils.m @@ -71,7 +71,7 @@ static AFURLSessionManager* manager; } BOOL check = [sha isEqualToString:localSHA]; - if (!check || ([getPreference(@"debug_logging") boolValue] && logSuccess)) { + if (!check || (getPrefBool(@"debug.debug_logging") && logSuccess)) { NSLog(@"[MCDL] SHA1 %@ for %@%@", (check ? @"passed" : @"failed"), (altName ? altName : path.lastPathComponent), @@ -81,7 +81,7 @@ static AFURLSessionManager* manager; } + (BOOL)checkSHA:(NSString *)sha forFile:(NSString *)path altName:(NSString *)altName logSuccess:(BOOL)logSuccess { - if ([getPreference(@"check_sha") boolValue]) { + if (getPrefBool(@"general.check_sha")) { return [self checkSHAIgnorePref:sha forFile:path altName:altName logSuccess:logSuccess]; } else { return [NSFileManager.defaultManager fileExistsAtPath:path]; @@ -140,9 +140,18 @@ static AFURLSessionManager* manager; BOOL isAssetIndex = NO; NSString *versionStr, *versionURL, *versionSHA; - if ([version isKindOfClass:[NSDictionary class]]) { + if ([version isKindOfClass:NSDictionary.class]) { isAssetIndex = [version valueForKey:@"totalSize"] != nil; + versionStr = [version valueForKey:@"id"]; + if ([versionStr isEqualToString:@"latest-release"]) { + versionStr = getPrefObject(@"internal.latest_version.release"); + } else if ([versionStr isEqualToString:@"latest-snapshot"]) { + versionStr = getPrefObject(@"internal.latest_version.snapshot"); + } + + // Find it again to resolve latest-* + version = [self findVersion:versionStr inList:remoteVersionList]; versionURL = [version valueForKey:@"url"]; versionSHA = versionURL.stringByDeletingLastPathComponent.lastPathComponent; } else { @@ -480,7 +489,7 @@ static AFURLSessionManager* manager; } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); callback(@"Finished: download assets", nil); - if ([getPreference(@"check_sha") boolValue]) { + if (getPrefBool(@"general.check_sha")) { NSLog(@"[MCDL] SHA1 passed for %d/%d asset files", verifiedCount, [assets[@"objects"] count]); if ([assets[@"objects"] count] - verifiedCount < 3) { NSLog(@"Note: some files of 1.19+ are skipped to workaround an issue"); @@ -489,7 +498,7 @@ static AFURLSessionManager* manager; return jobsAvailable != -1; } -+ (void)downloadVersion:(NSObject *)version callback:(MDCallback)callback { ++ (void)downloadVersion:(NSDictionary *)version callback:(MDCallback)callback { manager = [[AFURLSessionManager alloc] init]; NSProgress *mainProgress = [NSProgress progressWithTotalUnitCount:0]; [self downloadClientJson:version progress:mainProgress callback:callback success:^(NSMutableDictionary *json) { @@ -545,18 +554,7 @@ static AFURLSessionManager* manager; } + (NSObject *)findVersion:(NSString *)version inList:(NSArray *)list { - for (id object in list) { - NSString *item; - if ([object isKindOfClass:[NSDictionary class]]) { - item = [object valueForKey:@"id"]; - } else { - item = (NSString *)object; - } - if ([version isEqualToString:item]) { - return object; - } - } - return nil; + return [list filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(id == %@)", version]].firstObject; } + (NSObject *)findNearestVersion:(NSObject *)version expectedType:(int)type { diff --git a/Natives/PLPickerView.h b/Natives/PLPickerView.h new file mode 100644 index 00000000..4b622573 --- /dev/null +++ b/Natives/PLPickerView.h @@ -0,0 +1,15 @@ +#import + +@interface UIPickerView(private) +- (UITableViewCell * _Nonnull)tableView:(UITableView * _Nonnull)tableView cellForRowAtIndexPath:(NSIndexPath * _Nonnull)indexPath; +- (UITableView * _Nullable)tableViewForColumn:(NSInteger)column; +@end + +@interface PLPickerView : UIPickerView +- (UIImage * _Nullable)imageAtRow:(NSInteger)row column:(NSInteger)column; +@end + +@protocol PLPickerViewDelegate +@required +- (UIImage * _Nullable)pickerView:(PLPickerView * _Nonnull)pickerView imageForRow:(NSInteger)row forComponent:(NSInteger)component; +@end diff --git a/Natives/PLPickerView.m b/Natives/PLPickerView.m new file mode 100644 index 00000000..11ed8b5d --- /dev/null +++ b/Natives/PLPickerView.m @@ -0,0 +1,15 @@ +#import "PLPickerView.h" + +@implementation PLPickerView +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; + cell.imageView.image = [(id)self.delegate pickerView:self imageForRow:indexPath.row forComponent:indexPath.section]; + return cell; +} + +- (UIImage *)imageAtRow:(NSInteger)row column:(NSInteger)column { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:column]; + return [self tableView:[self tableViewForColumn:0] cellForRowAtIndexPath:indexPath].imageView.image; +} +@end diff --git a/Natives/PLPrefTableViewController.h b/Natives/PLPrefTableViewController.h index 98ccfb6f..e2fa7eb2 100644 --- a/Natives/PLPrefTableViewController.h +++ b/Natives/PLPrefTableViewController.h @@ -1,8 +1,8 @@ #import -typedef void(^CreateView)(UITableViewCell *, NSString *, NSDictionary *); -typedef id (^GetPreferenceBlock)(NSString *key); -typedef void (^SetPreferenceBlock)(NSString *key, id value); +typedef void(^CreateView)(UITableViewCell *, NSString *,NSString *, NSDictionary *); +typedef id (^GetPreferenceBlock)(NSString *, NSString *); +typedef void (^SetPreferenceBlock)(NSString *, NSString *, id); @interface PLPrefTableViewController : UITableViewController @@ -10,6 +10,7 @@ typedef void (^SetPreferenceBlock)(NSString *key, id value); @property(nonatomic) GetPreferenceBlock getPreference; @property(nonatomic) SetPreferenceBlock setPreference; +@property(nonatomic) BOOL prefSectionsVisible, hasDetail; @property(nonatomic) NSArray* prefSections; @property(nonatomic) NSMutableArray* prefSectionsVisibility; diff --git a/Natives/PLPrefTableViewController.m b/Natives/PLPrefTableViewController.m index 6273d820..a439349f 100644 --- a/Natives/PLPrefTableViewController.m +++ b/Natives/PLPrefTableViewController.m @@ -11,9 +11,6 @@ #import "ios_uikit_bridge.h" #import "utils.h" -#define sidebarNavController ((UINavigationController *)self.splitViewController.viewControllers[0]) -#define sidebarViewController ((LauncherMenuViewController *)sidebarNavController.viewControllers[0]) - @interface PLPrefTableViewController(){} @property(nonatomic) UIMenu* currentMenu; @property(nonatomic) UIBarButtonItem *helpBtn; @@ -34,9 +31,14 @@ self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped]; self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; - self.prefSectionsVisibility = [[NSMutableArray alloc] initWithCapacity:self.prefSections.count]; - for (int i = 0; i < self.prefSections.count; i++) { - [self.prefSectionsVisibility addObject:@NO]; + if (self.prefSections) { + self.prefSectionsVisibility = [[NSMutableArray alloc] initWithCapacity:self.prefSections.count]; + for (int i = 0; i < self.prefSections.count; i++) { + [self.prefSectionsVisibility addObject:@(self.prefSectionsVisible)]; + } + } else { + // Display one singe section if prefSection is unspecified + self.prefSectionsVisibility = (id)@[@YES]; } } @@ -50,8 +52,8 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - // Put navigation buttons back in place - if (self.navigationController) { + // Put navigation buttons back in place if we're first of the navigation controller + if (self.hasDetail) { self.navigationItem.rightBarButtonItems = @[[sidebarViewController drawAccountButton], [self drawHelpButton]]; } @@ -79,7 +81,7 @@ } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return self.prefSections.count; + return self.prefSectionsVisibility.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { @@ -105,6 +107,9 @@ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:cellStyle reuseIdentifier:cellID]; + cell.textLabel.adjustsFontSizeToFitWidth = YES; + cell.textLabel.numberOfLines = 0; + cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping; cell.detailTextLabel.numberOfLines = 0; cell.detailTextLabel.lineBreakMode = NSLineBreakByWordWrapping; } @@ -116,18 +121,20 @@ cell.detailTextLabel.text = nil; NSString *key = item[@"key"]; - if (indexPath.row == 0) { + if (indexPath.row == 0 && self.prefSections) { key = self.prefSections[indexPath.section]; cell.selectionStyle = UITableViewCellSelectionStyleDefault; cell.textLabel.text = localize(([NSString stringWithFormat:@"preference.section.%@", key]), nil); } else { CreateView createView = item[@"type"]; - createView(cell, key, item); + createView(cell, self.prefSections[indexPath.section], key, item); if (cell.accessoryView) { + objc_setAssociatedObject(cell.accessoryView, @"section", self.prefSections[indexPath.section], OBJC_ASSOCIATION_ASSIGN); objc_setAssociatedObject(cell.accessoryView, @"key", key, OBJC_ASSOCIATION_ASSIGN); objc_setAssociatedObject(cell.accessoryView, @"item", item, OBJC_ASSOCIATION_ASSIGN); } - cell.textLabel.text = localize(([NSString stringWithFormat:@"preference.title.%@", key]), nil); + cell.textLabel.text = localize((item[@"title"] ? item[@"title"] : + [NSString stringWithFormat:@"preference.title.%@", key]), nil); } // Set general properties @@ -156,57 +163,60 @@ - (void)initViewCreation { __weak PLPrefTableViewController *weakSelf = self; - self.typeButton = ^void(UITableViewCell *cell, NSString *key, NSDictionary *item) { + self.typeButton = ^void(UITableViewCell *cell, NSString *section, NSString *key, NSDictionary *item) { BOOL destructive = [item[@"destructive"] boolValue]; cell.selectionStyle = UITableViewCellSelectionStyleGray; cell.textLabel.textColor = destructive ? UIColor.systemRedColor : weakSelf.view.tintColor; }; - self.typeChildPane = ^void(UITableViewCell *cell, NSString *key, NSDictionary *item) { + self.typeChildPane = ^void(UITableViewCell *cell, NSString *section, NSString *key, NSDictionary *item) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.selectionStyle = UITableViewCellSelectionStyleGray; - cell.detailTextLabel.text = weakSelf.getPreference(key); + cell.detailTextLabel.text = weakSelf.getPreference(section, key); }; - self.typeTextField = ^void(UITableViewCell *cell, NSString *key, NSDictionary *item) { + self.typeTextField = ^void(UITableViewCell *cell, NSString *section, NSString *key, NSDictionary *item) { UITextField *view = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, cell.bounds.size.width / 2.1, cell.bounds.size.height)]; [view addTarget:view action:@selector(resignFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit]; + view.adjustsFontSizeToFitWidth = YES; view.autocorrectionType = UITextAutocorrectionTypeNo; view.autocapitalizationType = UITextAutocapitalizationTypeNone; view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin; + //view.contentVerticalAlignment = UIControlContentVerticalAlignmentTop; view.delegate = weakSelf; - view.placeholder = localize(([NSString stringWithFormat:@"preference.placeholder.%@", key]), nil); + //view.nonEditingLinebreakMode = NSLineBreakByCharWrapping; view.returnKeyType = UIReturnKeyDone; - view.text = weakSelf.getPreference(key); view.textAlignment = NSTextAlignmentRight; - view.adjustsFontSizeToFitWidth = YES; + view.placeholder = localize((item[@"placeholder"] ? item[@"placeholder"] : + [NSString stringWithFormat:@"preference.placeholder.%@", key]), nil); + view.text = weakSelf.getPreference(section, key); cell.accessoryView = view; }; - self.typePickField = ^void(UITableViewCell *cell, NSString *key, NSDictionary *item) { + self.typePickField = ^void(UITableViewCell *cell, NSString *section, NSString *key, NSDictionary *item) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.selectionStyle = UITableViewCellSelectionStyleGray; - cell.detailTextLabel.text = weakSelf.getPreference(key); + cell.detailTextLabel.text = weakSelf.getPreference(section, key); }; - self.typeSlider = ^void(UITableViewCell *cell, NSString *key, NSDictionary *item) { + self.typeSlider = ^void(UITableViewCell *cell, NSString *section, NSString *key, NSDictionary *item) { DBNumberedSlider *view = [[DBNumberedSlider alloc] initWithFrame:CGRectMake(0, 0, cell.bounds.size.width / 2.1, cell.bounds.size.height)]; [view addTarget:weakSelf action:@selector(sliderMoved:) forControlEvents:UIControlEventValueChanged]; view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin; view.minimumValue = [item[@"min"] intValue]; view.maximumValue = [item[@"max"] intValue]; view.continuous = YES; - view.value = [weakSelf.getPreference(key) intValue]; + view.value = [weakSelf.getPreference(section, key) intValue]; cell.accessoryView = view; }; - self.typeSwitch = ^void(UITableViewCell *cell, NSString *key, NSDictionary *item) { + self.typeSwitch = ^void(UITableViewCell *cell, NSString *section, NSString *key, NSDictionary *item) { UISwitch *view = [[UISwitch alloc] init]; NSArray *customSwitchValue = item[@"customSwitchValue"]; if (customSwitchValue == nil) { - [view setOn:[weakSelf.getPreference(key) boolValue] animated:NO]; + [view setOn:[weakSelf.getPreference(section, key) boolValue] animated:NO]; } else { - [view setOn:[weakSelf.getPreference(key) isEqualToString:customSwitchValue[1]] animated:NO]; + [view setOn:[weakSelf.getPreference(section, key) isEqualToString:customSwitchValue[1]] animated:NO]; } [view addTarget:weakSelf action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged]; cell.accessoryView = view; @@ -231,9 +241,9 @@ // Display warning if: warn condition is met and either one of these: // - does not have warnKey, always warn // - has warnKey and its value is YES, warn once and set it to NO - if (isWarnable && isWarnable(view) && (!warnKey || [self.getPreference(warnKey) boolValue])) { + if (isWarnable && isWarnable(view) && (!warnKey || [self.getPreference(@"warnings", warnKey) boolValue])) { if (warnKey) { - self.setPreference(warnKey, @NO); + self.setPreference(@"warnings", warnKey, @NO); } NSString *message = localize(([NSString stringWithFormat:@"preference.warn.%@", key]), nil); @@ -245,24 +255,23 @@ - (void)sliderMoved:(DBNumberedSlider *)sender { [self checkWarn:sender]; + NSString *section = objc_getAssociatedObject(sender, @"section"); NSString *key = objc_getAssociatedObject(sender, @"key"); sender.value = (int)sender.value; - self.setPreference(key, @(sender.value)); + self.setPreference(section, key, @(sender.value)); } - (void)switchChanged:(UISwitch *)sender { [self checkWarn:sender]; NSDictionary *item = objc_getAssociatedObject(sender, @"item"); + NSString *section = objc_getAssociatedObject(sender, @"section"); NSString *key = item[@"key"]; // Special switches may define custom value instead of NO/YES NSArray *customSwitchValue = item[@"customSwitchValue"]; - if (customSwitchValue != nil) { - self.setPreference(key, customSwitchValue[sender.isOn]); - } else { - self.setPreference(key, @(sender.isOn)); - } + self.setPreference(section, key, customSwitchValue ? + customSwitchValue[sender.isOn] : @(sender.isOn)); void(^invokeAction)(BOOL) = item[@"action"]; if (invokeAction) { @@ -279,7 +288,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; - if (indexPath.row == 0) { + if (indexPath.row == 0 && self.prefSections) { self.prefSectionsVisibility[indexPath.section] = @(![self.prefSectionsVisibility[indexPath.section] boolValue]); [tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade]; return; @@ -358,7 +367,7 @@ identifier:nil handler:^(UIAction *action) { cell.detailTextLabel.text = pickKeys[i]; - self.setPreference(item[@"key"], pickKeys[i]); + self.setPreference(self.prefSections[indexPath.section], item[@"key"], pickKeys[i]); void(^invokeAction)(NSString *) = item[@"action"]; if (invokeAction) { invokeAction(pickKeys[i]); @@ -368,8 +377,7 @@ self.currentMenu = [UIMenu menuWithTitle:message children:menuItems]; UIContextMenuInteraction *interaction = [[UIContextMenuInteraction alloc] initWithDelegate:self]; - cell.detailTextLabel.interactions = [NSArray new]; - [cell.detailTextLabel addInteraction:interaction]; + cell.detailTextLabel.interactions = @[interaction]; [interaction _presentMenuAtLocation:CGPointZero]; } @@ -415,9 +423,10 @@ - (void)textFieldDidEndEditing:(UITextField *)sender { [self checkWarn:sender]; + NSString *section = objc_getAssociatedObject(sender, @"section"); NSString *key = objc_getAssociatedObject(sender, @"key"); - self.setPreference(key, sender.text); + self.setPreference(section, key, sender.text); } @end diff --git a/Natives/PLPreferences.h b/Natives/PLPreferences.h new file mode 100644 index 00000000..4ef65b29 --- /dev/null +++ b/Natives/PLPreferences.h @@ -0,0 +1,16 @@ +#import + +@interface PLPreferences : NSObject + +@property(nonatomic) NSString *globalPath, *instancePath; +@property(nonatomic) NSMutableDictionary *globalPref, *instancePref; + +- (id)initWithGlobalPath:(NSString *)path; +- (id)initWithAutomaticMigrator; + +- (void)toggleIsolationForced:(BOOL)forced; +- (id)getObject:(NSString *)key; +- (BOOL)setObject:(NSString *)key value:(id)value; +- (void)reset; + +@end diff --git a/Natives/PLPreferences.m b/Natives/PLPreferences.m new file mode 100644 index 00000000..eee48b5c --- /dev/null +++ b/Natives/PLPreferences.m @@ -0,0 +1,260 @@ +#import "LauncherPreferences.h" +#import "PLPreferences.h" +#import "UIKit+hook.h" +#import "config.h" +#import "utils.h" + +@interface PLPreferences() +@end + +@implementation PLPreferences + ++ (id)defaultPrefForGlobal:(BOOL)global { + // Preferences that can be isolated + NSMutableDictionary *defaults = @{ + @"general": @{ + @"check_sha": @YES, + @"cosmetica": @YES, + @"debug_logging": @(!CONFIG_RELEASE), + }.mutableCopy, + @"video": @{ // Video & Audio + @"renderer": @"auto", + @"resolution": @(100), + @"max_framerate": @YES, + @"fullscreen_airplay": @YES, + @"silence_other_audio": @NO, + @"silence_with_switch": @NO + }.mutableCopy, + @"control": @{ + @"default_ctrl": @"default.json", + @"control_safe_area": UIApplication.sharedApplication ? NSStringFromUIEdgeInsets(getDefaultSafeArea()) : @"", + @"default_gamepad_ctrl": @"default.json", + @"controller_type": @"xbox", + @"hardware_hide": @YES, + @"gesture_mouse": @YES, + @"gesture_hotbar": @YES, + @"disable_haptics": @NO, + @"slideable_hotbar": @NO, + @"press_duration": @(400), + @"button_scale": @(100), + @"mouse_scale": @(100), + @"mouse_speed": @(100), + @"virtmouse_enable": @NO, + @"gyroscope_enable": @NO, + @"gyroscope_invert_x_axis": @NO, + @"gyroscope_sensitivity": @(100) + }.mutableCopy, + @"java": @{ + @"java_homes": @{ + @"0": @{ + @"1_16_5_older": @"8", + @"1_17_newer": @"17", + @"install_jar": @"8" + }.mutableCopy, + @"8": @"internal", + @"17": @"internal" + }.mutableCopy, + @"java_args": @"", + @"env_variables": @"", + @"auto_ram": @(!getEntitlementValue(@"com.apple.private.memorystatus")), + @"allocated_memory": [NSNumber numberWithFloat:roundf((NSProcessInfo.processInfo.physicalMemory / 1048576) * 0.25)] + }.mutableCopy, + @"internal": @{ + @"isolated": @NO, + @"latest_version": [NSDictionary new] + }.mutableCopy + }.mutableCopy; + + if (global) { + // Preferences that cannot be isolated + NSDictionary *general = @{ + @"game_directory": @"default", + @"hidden_sidebar": @(realUIIdiom == UIUserInterfaceIdiomPhone), + @"jitstreamer_server": @"69.69.0.1", + @"enable_altkit": @YES, + @"appicon": @"AppIcon-Light" + }; + [defaults[@"general"] addEntriesFromDictionary:general]; + + defaults[@"java"][@"manage_runtime"] = @""; // stub + defaults[@"debug"] = @{ + @"debug_skip_wait_jit": @NO, + @"debug_hide_home_indicator": @NO, + @"debug_ipad_ui": @(realUIIdiom == UIUserInterfaceIdiomPad), + @"debug_auto_correction": @YES, + @"debug_show_layout_bounds": @NO, + @"debug_show_layout_overlap": @NO + }.mutableCopy; + defaults[@"warnings"] = @{ + @"local_warn": @YES, + @"mem_warn": @YES, + @"auto_ram_warn": @YES, + @"limited_ram_warn": @YES + }.mutableCopy; + // TODO: isolate this or add account picker into profile editor(?) + defaults[@"internal"][@"selected_account"] = @""; + } + + return defaults; +} + ++ (id)getPreference:(NSString *)key from:(NSDictionary *)pref { + for (NSDictionary *section in pref.allValues) { + if ([section isKindOfClass:NSDictionary.class] && section[key]) { + return section[key]; + } + } + return nil; +} + ++ (id)getOldLayoutPreference:(NSString *)key from:(NSDictionary *)pref { + // Find preference in the root dictionary first + if (pref[key]) { + return pref[key]; + } + // Find preference in subdictionaries + id value = [self getPreference:key from:pref]; + if (!value) { + NSLog(@"[PLPreferences] Migrator could not find preference %@", key); + } + return value; +} + +- (id)initWithGlobalPath:(NSString *)path { + self = [super init]; + self.globalPath = path; + self.globalPref = [NSMutableDictionary dictionaryWithContentsOfFile:path]; + [self saveGlobalPref]; + return self; +} + +- (id)initWithAutomaticMigrator { + self = [super init]; + self.globalPath = [@(getenv("POJAV_HOME")) stringByAppendingPathComponent:@"launcher_preferences_v2.plist"]; + NSMutableDictionary *pref = [NSMutableDictionary dictionaryWithContentsOfFile:self.globalPath]; + + NSString *oldPath = [@(getenv("POJAV_HOME")) stringByAppendingPathComponent:@"launcher_preferences.plist"]; + NSMutableDictionary *oldPref = [NSMutableDictionary dictionaryWithContentsOfFile:oldPath]; + + if (pref || !oldPref[@"env_vars"]) { + // Initialize or load existing v2 layout + self.globalPref = pref; + } else { + NSDebugLog(@"[PLPreferences] Migrating to %@", self.globalPath.lastPathComponent); + // Perform migration from v1 layout + self.globalPref = [NSMutableDictionary new]; + for (NSString *section in self.globalPref.allKeys) { + for (NSString *key in self.globalPref[section].allKeys) { + id value = [PLPreferences getOldLayoutPreference:key from:oldPref]; + if (value) { + self.globalPref[section][key] = value; + } + } + } + } + + [self saveGlobalPref]; + return self; +} + +- (id)setDefaultsForPref:(NSMutableDictionary *)pref global:(BOOL)global { + NSMutableDictionary *defaults = [PLPreferences defaultPrefForGlobal:global]; + if (!pref) { + NSLog(@"[PLPreferences] Initializing default values for %@ preferences", global ? @"global" : @"isolated"); + return defaults; + } + + for (NSString *section in defaults.allKeys) { + if (!pref[section]) { + NSDebugLog(@"[PLPreferences] Set default values for section %@", section); + pref[section] = defaults[section]; + continue; + } + for (NSString *key in defaults[section].allKeys) { + if (pref[section][key]) continue; + id value = defaults[section][key]; + NSDebugLog(@"[PLPreferences] Set default vaule: %@: %@", key, value); + pref[section][key] = value; + } + } + return pref; +} + +- (void)setGlobalPref:(NSMutableDictionary *)pref { + _globalPref = [self setDefaultsForPref:pref global:YES]; +} + +- (void)setInstancePref:(NSMutableDictionary *)pref { + _instancePref = [self setDefaultsForPref:pref global:NO]; +} + +- (void)toggleIsolationForced:(BOOL)force { + NSMutableDictionary *instancePref = [NSMutableDictionary dictionaryWithContentsOfFile:self.instancePath]; + if (force || [instancePref[@"internal"][@"isolated"] boolValue]) { + NSLog(@"[PLPreferences] Using isolated preferences from %@", self.instancePath.stringByResolvingSymlinksInPath); + self.instancePref = instancePref; + if (!instancePref) { + // Copy preferences from the global one + for (NSString *section in self.instancePref) { + for (NSString *key in self.instancePref[section].allKeys) { + self.instancePref[section][key] = self.globalPref[section][key]; + } + } + } + + // Declare that itself is isolated + self.instancePref[@"internal"][@"isolated"] = @YES; + + [self saveInstancePref]; + } else if (self.instancePref) { + NSLog(@"[PLPreferences] Using global preferences"); + _instancePref = nil; + } +} + +- (id)getObject:(NSString *)key { + id value = [self.instancePref valueForKeyPath:key]; + if (!value) { + value = [self.globalPref valueForKeyPath:key]; + } + if (!value) { + NSLog(@"[PLPreferences] Getter could not find preference %@", key); + } + return value; +} + +- (BOOL)setObject:(NSString *)key value:(id)value { + if ([self.instancePref valueForKeyPath:key]) { + [self.instancePref setValue:value forKeyPath:key]; + [self saveInstancePref]; + return YES; + } else if ([self.globalPref valueForKeyPath:key]) { + [self.globalPref setValue:value forKeyPath:key]; + [self saveGlobalPref]; + return YES; + } + NSLog(@"[PLPreferences] Setter could not find preference %@", key); + return NO; +} + +- (void)reset { + if (self.instancePref) { + [NSFileManager.defaultManager removeItemAtPath:self.instancePath error:nil]; + [self toggleIsolationForced:YES]; + // Only reset isolated values + return; + } + + self.globalPref = nil; + [self saveGlobalPref]; +} + +- (void)saveGlobalPref { + [self.globalPref writeToFile:self.globalPath atomically:YES]; +} + +- (void)saveInstancePref { + [self.instancePref writeToFile:self.instancePath atomically:YES]; +} + +@end diff --git a/Natives/PLProfiles.h b/Natives/PLProfiles.h new file mode 100644 index 00000000..3ece9468 --- /dev/null +++ b/Natives/PLProfiles.h @@ -0,0 +1,22 @@ +#import + +@interface PLProfiles : NSObject + +@property(nonatomic) NSString *profilePath; +@property(nonatomic) NSMutableDictionary *> *> *profileDict; + ++ (PLProfiles *)current; ++ (void)updateCurrent; + +//+ (id)profile:(NSMutableDictionary *)profile resolveKey:(id)key; ++ (NSString *)resolveKeyForCurrentProfile:(id)key; + +- (id)initWithCurrentInstance; +- (NSMutableDictionary *> *)profiles; + +- (NSMutableDictionary *)selectedProfile; +- (NSString *)selectedProfileName; +- (void)setSelectedProfileName:(NSString *)name; +- (void)save; + +@end diff --git a/Natives/PLProfiles.m b/Natives/PLProfiles.m new file mode 100644 index 00000000..6e73a525 --- /dev/null +++ b/Natives/PLProfiles.m @@ -0,0 +1,96 @@ +#import "LauncherPreferences.h" +#import "PLProfiles.h" +#import "utils.h" + +static PLProfiles* current; + +@interface PLProfiles() +@end + +@implementation PLProfiles + ++ (id)defaultProfiles { + return @{ + @"profiles": @{ + @"(Default)": @{ + @"name": @"(Default)", + @"lastVersionId": @"latest-release" + } + }, + @"selectedProfile": @"(Default)" + }.mutableCopy; +} + ++ (PLProfiles *)current { + if (!current) { + [self updateCurrent]; + } + return current; +} + ++ (void)updateCurrent { + current = [[PLProfiles alloc] initWithCurrentInstance]; +} + ++ (id)profile:(NSMutableDictionary *)profile resolveKey:(id)key { + NSString *value = profile[key]; + if (value.length > 0) { + //NSDebugLog(@"[PLProfiles] Applying %@: \"%@\"", key, value); + return value; + } + + NSDictionary *valueDefaults = @{ + @"javaVersion": @"0", + @"gameDir": @"." + }; + if (valueDefaults[key]) { + return valueDefaults[key]; + } + + NSDictionary *prefDefaults = @{ + @"defaultTouchCtrl": @"control.default_ctrl", + @"defaultGamepadCtrl": @"control.default_gamepad_ctrl", + @"javaArgs": @"java.java_args", + @"renderer": @"video.renderer" + }; + return getPrefObject(prefDefaults[key]); +} + ++ (id)resolveKeyForCurrentProfile:(id)key { + return [self profile:self.current.selectedProfile resolveKey:key]; +} + +- (id)initWithCurrentInstance { + self = [super init]; + self.profilePath = [@(getenv("POJAV_GAME_DIR")) stringByAppendingPathComponent:@"launcher_profiles.json"]; + self.profileDict = parseJSONFromFile(self.profilePath); + if (self.profileDict[@"error"]) { + self.profileDict = PLProfiles.defaultProfiles; + [self save]; + } + + return self; +} + +- (id)profiles { + return self.profileDict[@"profiles"]; +} + +- (id)selectedProfile { + return self.profiles[self.selectedProfileName]; +} + +- (NSString *)selectedProfileName { + return (id)self.profileDict[@"selectedProfile"]; +} + +- (void)setSelectedProfileName:(NSString *)name { + self.profileDict[@"selectedProfile"] = (id)name; + [self save]; +} + +- (void)save { + saveJSONToFile(self.profileDict, self.profilePath); +} + +@end diff --git a/Natives/SceneExternalDelegate.m b/Natives/SceneExternalDelegate.m index 5d6ba3cb..540f072f 100644 --- a/Natives/SceneExternalDelegate.m +++ b/Natives/SceneExternalDelegate.m @@ -15,7 +15,7 @@ extern UIWindow* currentWindow(); UIWindowScene *windowScene = (UIWindowScene *)scene; self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; self.window.frame = windowScene.coordinateSpace.bounds; - if (SurfaceViewController.isRunning && [getPreference(@"fullscreen_airplay") boolValue]) { + if (SurfaceViewController.isRunning && getPrefBool(@"video.fullscreen_airplay")) { [currentWindow().rootViewController performSelector:@selector(switchToExternalDisplay)]; } } @@ -26,7 +26,7 @@ extern UIWindow* currentWindow(); // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). - if (SurfaceViewController.isRunning && [getPreference(@"fullscreen_airplay") boolValue]) { + if (SurfaceViewController.isRunning && getPrefBool(@"video.fullscreen_airplay")) { [currentWindow().rootViewController performSelector:@selector(switchToInternalDisplay)]; } } diff --git a/Natives/SurfaceViewController+Navigation.m b/Natives/SurfaceViewController+Navigation.m index e6d499e6..91f9df9e 100644 --- a/Natives/SurfaceViewController+Navigation.m +++ b/Natives/SurfaceViewController+Navigation.m @@ -1,6 +1,7 @@ #import "CustomControlsViewController.h" #import "LauncherPreferences.h" #import "LauncherPreferencesViewController.h" +#import "PLProfiles.h" #import "SurfaceViewController.h" #import "utils.h" @@ -132,6 +133,18 @@ static CGPoint lastCenterPoint; [self.ctrlView removeAllButtons]; CustomControlsViewController *vc = [[CustomControlsViewController alloc] init]; vc.modalPresentationStyle = UIModalPresentationOverFullScreen; + vc.setDefaultCtrl = ^(NSString *name){ + if (PLProfiles.current.selectedProfile[@"defaultTouchCtrl"]) { + // Save default to current profile + PLProfiles.current.selectedProfile[@"defaultTouchCtrl"] = name; + } else { + // Save default to preferences + setPrefObject(@"control.default_ctrl", name); + } + }; + vc.getDefaultCtrl = ^{ + return [PLProfiles resolveKeyForCurrentProfile:@"defaultTouchCtrl"]; + }; [self presentViewController:vc animated:NO completion:nil]; } @@ -152,7 +165,7 @@ static CGPoint lastCenterPoint; - (BOOL)prefersHomeIndicatorAutoHidden { return self.menuView.hidden && - [getPreference(@"debug_hide_home_indicator") boolValue]; + getPrefBool(@"debug.debug_hide_home_indicator"); } - (BOOL)prefersStatusBarHidden { diff --git a/Natives/SurfaceViewController.m b/Natives/SurfaceViewController.m index 8bde4615..17821d19 100644 --- a/Natives/SurfaceViewController.m +++ b/Natives/SurfaceViewController.m @@ -19,6 +19,7 @@ #import "JavaLauncher.h" #import "LauncherPreferences.h" #import "MinecraftResourceUtils.h" +#import "PLProfiles.h" #import "SurfaceViewController.h" #import "TrackedTextField.h" #import "UIKit+hook.h" @@ -59,7 +60,7 @@ BOOL slideableHotbar; @property(nonatomic) CGFloat screenScale; @property(nonatomic) CGFloat mouseSpeed; @property(nonatomic) CGRect clickRange; -@property(nonatomic) BOOL shouldTriggerClick; +@property(nonatomic) BOOL shouldTriggerClick, shouldTriggerHaptic; @property(nonatomic) BOOL enableMouseGestures, enableHotbarGestures; @@ -78,11 +79,9 @@ BOOL slideableHotbar; self.lightHaptic = [[UIImpactFeedbackGenerator alloc] initWithStyle:(UIImpactFeedbackStyleLight)]; self.mediumHaptic = [[UIImpactFeedbackGenerator alloc] initWithStyle:(UIImpactFeedbackStyleMedium)]; - setPreference(@"internal_launch_on_boot", @(NO)); - isUseStackQueueCall = [getPreference(@"internal_useStackQueue") boolValue]; - setPreference(@"internal_useStackQueue", nil); + //setPrefBool(@"internal.internal_launch_on_boot", NO); - [[UIApplication sharedApplication] setIdleTimerDisabled:YES]; + UIApplication.sharedApplication.idleTimerDisabled = YES; BOOL isTVOS = realUIIdiom == UIUserInterfaceIdiomTV; if (!isTVOS) { [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; @@ -96,7 +95,7 @@ BOOL slideableHotbar; }; CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:tickInput selector:@selector(invoke)]; if (@available(iOS 15.0, tvOS 15.0, *)) { - if([getPreference(@"max_framerate") boolValue]) { + if(getPrefBool(@"video.max_framerate")) { displayLink.preferredFrameRateRange = CAFrameRateRangeMake(30, 120, 120); } else { displayLink.preferredFrameRateRange = CAFrameRateRangeMake(30, 60, 60); @@ -168,7 +167,7 @@ BOOL slideableHotbar; } // Virtual mouse - virtualMouseEnabled = [getPreference(@"virtmouse_enable") boolValue]; + virtualMouseEnabled = getPrefBool(@"control.virtmouse_enable"); virtualMouseFrame = CGRectMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2, 18, 27); self.mousePointerView = [[UIImageView alloc] initWithFrame:virtualMouseFrame]; self.mousePointerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin |UIViewAutoresizingFlexibleBottomMargin; @@ -193,8 +192,6 @@ BOOL slideableHotbar; CallbackBridge_nativeSendKey(key, scancode, action, mods); }; - NSString *controlFilePath = [NSString stringWithFormat:@"%s/controlmap/%@", getenv("POJAV_HOME"), (NSString *)getPreference(@"default_ctrl")]; - self.swipeableButtons = [[NSMutableArray alloc] init]; [KeyboardInput initKeycodeTable]; @@ -205,7 +202,7 @@ BOOL slideableHotbar; self.mousePointerView.hidden = isGrabbing; virtualMouseEnabled = YES; [self setNeedsUpdateOfPrefersPointerLocked]; - if([getPreference(@"hardware_hide") boolValue]) { + if (getPrefBool(@"control.hardware_hide")) { self.ctrlView.hidden = YES; } }]; @@ -217,7 +214,7 @@ BOOL slideableHotbar; mouse.mouseInput.middleButton.pressedChangedHandler = nil; mouse.mouseInput.rightButton.pressedChangedHandler = nil; [self setNeedsUpdateOfPrefersPointerLocked]; - if([getPreference(@"hardware_hide") boolValue]) { + if (getPrefBool(@"controll.hardware_hide")) { self.ctrlView.hidden = NO; } }]; @@ -234,7 +231,7 @@ BOOL slideableHotbar; [ControllerInput registerControllerCallbacks:controller]; self.mousePointerView.hidden = isGrabbing; virtualMouseEnabled = YES; - if([getPreference(@"hardware_hide") boolValue]) { + if (getPrefBool(@"control.hardware_hide")) { self.ctrlView.hidden = YES; } }]; @@ -242,7 +239,7 @@ BOOL slideableHotbar; NSLog(@"Input: Controller disconnected!"); GCController* controller = note.object; [ControllerInput unregisterControllerCallbacks:controller]; - if([getPreference(@"hardware_hide") boolValue]) { + if (getPrefBool(@"control.hardware_hide")) { self.ctrlView.hidden = NO; } }]; @@ -268,7 +265,7 @@ BOOL slideableHotbar; #endif if (UIApplication.sharedApplication.connectedScenes.count > 1 && - [getPreference(@"fullscreen_airplay") boolValue]) { + getPrefBool(@"video.fullscreen_airplay")) { [self switchToExternalDisplay]; } @@ -284,18 +281,18 @@ BOOL slideableHotbar; NSError *sessionError = nil; AVAudioSessionCategory category; AVAudioSessionCategoryOptions options; - if([getPreference(@"silence_with_switch") boolValue]) { + if(getPrefBool(@"video.silence_with_switch")) { category = AVAudioSessionCategorySoloAmbient; } else { category = AVAudioSessionCategoryPlayback; } - if([getPreference(@"silence_other_audio") boolValue]) { + if(getPrefBool(@"video.silence_other_audio")) { options = 0; } else { options = AVAudioSessionCategoryOptionMixWithOthers; } - [[AVAudioSession sharedInstance] setCategory:category withOptions:options error:&sessionError]; - [[AVAudioSession sharedInstance] setActive:YES error:&sessionError]; + [AVAudioSession.sharedInstance setCategory:category withOptions:options error:&sessionError]; + [AVAudioSession.sharedInstance setActive:YES error:&sessionError]; } - (void)updateJetsamControl { @@ -303,7 +300,7 @@ BOOL slideableHotbar; return; } // More 1024MB is necessary for other memory regions (native, Java GC, etc.) - int limit = [getPreference(@"allocated_memory") intValue] + 1024; + int limit = getPrefInt(@"java.allocated_memory") + 1024; if (memorystatus_control(MEMORYSTATUS_CMD_SET_JETSAM_TASK_LIMIT, getpid(), limit, NULL, 0) == -1) { NSLog(@"Failed to set Jetsam task limit: error: %s", strerror(errno)); } else { @@ -313,37 +310,38 @@ BOOL slideableHotbar; - (void)updatePreferenceChanges { // Update UITextField auto correction - if ([getPreference(@"debug_auto_correction") boolValue]) { + if (getPrefBool(@"debug.debug_auto_correction")) { self.inputTextField.autocorrectionType = UITextAutocorrectionTypeDefault; } else { self.inputTextField.autocorrectionType = UITextAutocorrectionTypeNo; } - BOOL gyroEnabled = [getPreference(@"gyroscope_enable") boolValue]; - BOOL gyroInvertX = [getPreference(@"gyroscope_invert_x_axis") boolValue]; - int gyroSensitivity = [getPreference(@"gyroscope_sensitivity") intValue]; + BOOL gyroEnabled = getPrefBool(@"control.gyroscope_enable"); + BOOL gyroInvertX = getPrefBool(@"control.gyroscope_invert_x_axis"); + int gyroSensitivity = getPrefInt(@"control.gyroscope_sensitivity"); [GyroInput updateSensitivity:gyroEnabled?gyroSensitivity:0 invertXAxis:gyroInvertX]; - self.mouseSpeed = [getPreference(@"mouse_speed") floatValue] / 100.0; + self.mouseSpeed = getPrefFloat(@"control.mouse_speed") / 100.0; - virtualMouseEnabled = [getPreference(@"virtmouse_enable") boolValue]; + virtualMouseEnabled = getPrefBool(@"control.virtmouse_enable"); self.mousePointerView.hidden = isGrabbing || !virtualMouseEnabled; // Update virtual mouse scale - CGFloat mouseScale = [getPreference(@"mouse_scale") floatValue] / 100.0; + CGFloat mouseScale = getPrefFloat(@"control.mouse_scale") / 100.0; virtualMouseFrame = CGRectMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2, 18.0 * mouseScale, 27 * mouseScale); self.mousePointerView.frame = virtualMouseFrame; self.ctrlView.frame = getSafeArea(); // Update gestures state - slideableHotbar = [getPreference(@"slideable_hotbar") boolValue]; - self.enableMouseGestures = [getPreference(@"gesture_mouse") boolValue]; - self.enableHotbarGestures = [getPreference(@"gesture_hotbar") boolValue]; + slideableHotbar = getPrefBool(@"control.slideable_hotbar"); + self.enableMouseGestures = getPrefBool(@"control.gesture_mouse"); + self.enableHotbarGestures = getPrefBool(@"control.gesture_hotbar"); + self.shouldTriggerHaptic = !getPrefBool(@"control.disable_haptics"); self.scrollPanGesture.enabled = self.enableMouseGestures; self.doubleTapGesture.enabled = self.enableHotbarGestures; - self.longPressGesture.minimumPressDuration = [getPreference(@"press_duration") floatValue] / 1000.0; + self.longPressGesture.minimumPressDuration = getPrefFloat(@"control.press_duration") / 1000.0; // Update audio settings [self updateAudioSettings]; @@ -363,7 +361,7 @@ BOOL slideableHotbar; self.surfaceView.frame = self.surfaceView.superview.frame; } - resolutionScale = [getPreference(@"resolution") floatValue] / 100.0; + resolutionScale = getPrefFloat(@"video.resolution") / 100.0; self.surfaceView.layer.contentsScale = self.screenScale * resolutionScale; physicalWidth = roundf(self.surfaceView.frame.size.width * self.screenScale); @@ -382,7 +380,8 @@ BOOL slideableHotbar; - (void)launchMinecraft { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [MinecraftResourceUtils downloadClientJson:getPreference(@"selected_version") progress:nil callback:nil success:^(NSMutableDictionary *json) { + NSDictionary *version = @{@"id": PLProfiles.current.selectedProfile[@"lastVersionId"]}; + [MinecraftResourceUtils downloadClientJson:version progress:nil callback:nil success:^(NSMutableDictionary *json) { [MinecraftResourceUtils processJVMArgs:json]; launchJVM( BaseAuthenticator.current.authData[@"username"], @@ -397,7 +396,8 @@ BOOL slideableHotbar; - (void)loadCustomControls { self.edgeGesture.enabled = YES; [self.swipeableButtons removeAllObjects]; - [self.ctrlView loadControlFile:getPreference(@"default_ctrl")]; + NSString *controlFile = [PLProfiles resolveKeyForCurrentProfile:@"defaultTouchCtrl"]; + [self.ctrlView loadControlFile:controlFile]; for (ControlButton *button in self.ctrlView.subviews) { BOOL isSwipeable = [button.properties[@"isSwipeable"] boolValue]; @@ -623,7 +623,7 @@ BOOL slideableHotbar; - (void)surfaceOnClick:(UITapGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateEnded){ - if(![getPreference(@"disable_haptics") boolValue]) { + if(self.shouldTriggerHaptic) { [self.lightHaptic impactOccurred]; } } @@ -650,7 +650,7 @@ BOOL slideableHotbar; - (void)surfaceOnDoubleClick:(UITapGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateEnded){ - if(![getPreference(@"disable_haptics") boolValue]) { + if(self.shouldTriggerHaptic) { [self.lightHaptic impactOccurred]; } } @@ -697,7 +697,7 @@ BOOL slideableHotbar; -(void)surfaceOnLongpress:(UILongPressGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateEnded){ - if(![getPreference(@"disable_haptics") boolValue]) { + if(self.shouldTriggerHaptic) { [self.mediumHaptic impactOccurred]; } } @@ -735,7 +735,7 @@ BOOL slideableHotbar; - (void)surfaceOnTouchesScroll:(UIPanGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateEnded){ - if(![getPreference(@"disable_haptics") boolValue]) { + if(self.shouldTriggerHaptic) { [self.lightHaptic impactOccurred]; } } @@ -823,7 +823,7 @@ int currentVisibility = 1; if (!isGrabbing && !held) { virtualMouseEnabled = !virtualMouseEnabled; self.mousePointerView.hidden = !virtualMouseEnabled; - setPreference(@"virtmouse_enable", @(virtualMouseEnabled)); + setPrefBool(@"control.virtmouse_enable", virtualMouseEnabled); } break; @@ -848,7 +848,7 @@ int currentVisibility = 1; - (void)executebtn_down:(ControlButton *)sender { - if(![getPreference(@"disable_haptics") boolValue]) { + if(self.shouldTriggerHaptic) { [self.lightHaptic impactOccurred]; } @@ -899,8 +899,8 @@ int currentVisibility = 1; sender.backgroundColor = sender.savedBackgroundColor; [self executebtn:sender withAction:ACTION_UP]; } - - if(![getPreference(@"disable_haptics") boolValue]) { + + if(self.shouldTriggerHaptic) { [self.lightHaptic impactOccurred]; } } diff --git a/Natives/UIKit+hook.h b/Natives/UIKit+hook.h index 401c48dd..c485c884 100644 --- a/Natives/UIKit+hook.h +++ b/Natives/UIKit+hook.h @@ -18,3 +18,13 @@ @interface UIImage(private) - (UIImage *)_imageWithSize:(CGSize)size; @end + +@interface UITextField(private) +@property(assign, nonatomic) NSInteger nonEditingLinebreakMode; +@end + +/* +@interface WFTextTokenTextView : UITextField +@property(nonatomic) NSString* placeholder +@end +*/ diff --git a/Natives/UIKit+hook.m b/Natives/UIKit+hook.m index 19231ccd..4abd8dab 100644 --- a/Natives/UIKit+hook.m +++ b/Natives/UIKit+hook.m @@ -34,7 +34,7 @@ void init_hookUIKitConstructor(void) { @implementation UIDevice(hook) - (UIUserInterfaceIdiom)hook_userInterfaceIdiom { - if ([getPreference(@"debug_ipad_ui") boolValue]) { + if (getPrefBool(@"debug.debug_ipad_ui")) { return UIUserInterfaceIdiomPad; } else if (self.hook_userInterfaceIdiom == UIUserInterfaceIdiomTV) { return self.hook_userInterfaceIdiom; @@ -103,7 +103,7 @@ const NSString *cornerLayerKey = @"cornerLayer"; 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]) { + if (getPrefBool(@"debug.debug_show_layout_overlap")) { cornerLayer.fillColor = [UIColor.yellowColor colorWithAlphaComponent:0.1].CGColor; } else { cornerLayer.fillColor = nil; diff --git a/Natives/authenticator/BaseAuthenticator.m b/Natives/authenticator/BaseAuthenticator.m index 08ae737c..6253be31 100644 --- a/Natives/authenticator/BaseAuthenticator.m +++ b/Natives/authenticator/BaseAuthenticator.m @@ -9,7 +9,7 @@ static BaseAuthenticator *current = nil; + (id)current { if (current == nil) { - [self loadSavedName:getPreference(@"selected_account")]; + [self loadSavedName:getPrefObject(@"internal.selected_account")]; } return current; } diff --git a/Natives/customcontrols/ControlButton.m b/Natives/customcontrols/ControlButton.m index 374e42f9..76632d26 100644 --- a/Natives/customcontrols/ControlButton.m +++ b/Natives/customcontrols/ControlButton.m @@ -88,7 +88,7 @@ - (void)preProcessProperties { CGFloat currentScale = [((ControlLayout *)self.superview).layoutDictionary[@"scaledAt"] floatValue]; - CGFloat savedScale = [getPreference(@"button_scale") floatValue]; + CGFloat savedScale = getPrefFloat(@"control.button_scale"); if (currentScale != savedScale) { self.properties[@"width"] = @([self.properties[@"width"] floatValue] * savedScale / currentScale); self.properties[@"height"] = @([self.properties[@"height"] floatValue] * savedScale / currentScale); @@ -116,7 +116,7 @@ INSERT_VALUE("screen_width", ([NSString stringWithFormat:@"%f", screenWidth])); INSERT_VALUE("screen_height", ([NSString stringWithFormat:@"%f", screenHeight])); INSERT_VALUE("margin", ([NSString stringWithFormat:@"%f", 2.0 * screenScale])); - INSERT_VALUE("preferred_scale", ([NSString stringWithFormat:@"%f", [getPreference(@"button_scale") floatValue]])); + INSERT_VALUE("preferred_scale", ([NSString stringWithFormat:@"%f", getPrefFloat(@"control.button_scale")])); // NSLog(@"After insert: %@", string); @@ -304,9 +304,9 @@ NSString *str = [equation stringByReplacingOccurrencesOfString:@"${right}" withString:@"(${screen_width} - ${width})"]; str = [str stringByReplacingOccurrencesOfString:@"${bottom}" withString:@"(${screen_height} - ${height})"]; // "(px(" + Tools.pxToDp(button.getProperties().getHeight()) + ") /" + PREF_BUTTONSIZE + " * ${preferred_scale})" - str = [str stringByReplacingOccurrencesOfString:@"${height}" withString:[NSString stringWithFormat:@"(px(%f) / %f * ${preferred_scale})", button.frame.size.height, [getPreference(@"button_scale") floatValue]]]; + str = [str stringByReplacingOccurrencesOfString:@"${height}" withString:[NSString stringWithFormat:@"(px(%f) / %f * ${preferred_scale})", button.frame.size.height, getPrefFloat(@"control.button_scale")]]; // "(px(" + Tools.pxToDp(button.getProperties().getWidth()) + ") / " + PREF_BUTTONSIZE + " * ${preferred_scale})" - str = [str stringByReplacingOccurrencesOfString:@"${width}" withString:[NSString stringWithFormat:@"(px(%f) / %f * ${preferred_scale})", button.frame.size.width, [getPreference(@"button_scale") floatValue]]]; + str = [str stringByReplacingOccurrencesOfString:@"${width}" withString:[NSString stringWithFormat:@"(px(%f) / %f * ${preferred_scale})", button.frame.size.width, getPrefFloat(@"control.button_scale")]]; return str; } diff --git a/Natives/customcontrols/ControlLayout.m b/Natives/customcontrols/ControlLayout.m index ce3c0b03..dc551593 100644 --- a/Natives/customcontrols/ControlLayout.m +++ b/Natives/customcontrols/ControlLayout.m @@ -15,7 +15,7 @@ self.layoutDictionary = layoutDictionary; CGFloat currentScale = [self.layoutDictionary[@"scaledAt"] floatValue]; - CGFloat savedScale = [getPreference(@"button_scale") floatValue]; + CGFloat savedScale = getPrefFloat(@"control.button_scale"); loadControlObject(self, self.layoutDictionary); self.layoutDictionary[@"scaledAt"] = @(savedScale); @@ -76,7 +76,7 @@ [(ControlButton *)view update]; } - CGFloat savedScale = [getPreference(@"button_scale") floatValue]; + CGFloat savedScale = getPrefFloat(@"control.button_scale"); self.layoutDictionary[@"scaledAt"] = @(savedScale); } diff --git a/Natives/customcontrols/CustomControlsUtils.m b/Natives/customcontrols/CustomControlsUtils.m index 4e6229d8..64e26916 100644 --- a/Natives/customcontrols/CustomControlsUtils.m +++ b/Natives/customcontrols/CustomControlsUtils.m @@ -371,7 +371,7 @@ void loadControlObject(UIView* targetView, NSMutableDictionary* controlDictionar if (convertLayoutIfNecessary(controlDictionary)) { NSMutableArray *controlDataList = controlDictionary[@"mControlDataList"]; - setPreference(@"internal_current_button_scale", controlDictionary[@"scaledAt"]); + //setPrefObject(@"internal.internal_current_button_scale", controlDictionary[@"scaledAt"]); for (NSMutableDictionary *buttonDict in controlDataList) { //APPLY_SCALE(buttonDict[@"strokeWidth"]); @try { @@ -404,7 +404,7 @@ void loadControlObject(UIView* targetView, NSMutableDictionary* controlDictionar [drawer update]; } - controlDictionary[@"scaledAt"] = @([getPreference(@"button_scale") floatValue]); + controlDictionary[@"scaledAt"] = getPrefObject(@"control.button_scale"); if (errorString.length > 0) { showDialog(@"Error processing dynamic position", errorString); diff --git a/Natives/input/ControllerInput.m b/Natives/input/ControllerInput.m index c025e5cf..eb18bf39 100644 --- a/Natives/input/ControllerInput.m +++ b/Natives/input/ControllerInput.m @@ -1,5 +1,6 @@ #import "ControllerInput.h" #import "../LauncherPreferences.h" +#import "../PLProfiles.h" #import "../SurfaceViewController.h" #import "../utils.h" @@ -30,7 +31,8 @@ BOOL leftShiftHeld; return; } - NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/%@", getenv("POJAV_HOME"), getPreference(@"default_gamepad_ctrl")]; + NSString *controlFile = [PLProfiles resolveKeyForCurrentProfile:@"defaultGamepadCtrl"]; + NSString *gamepadPath = [NSString stringWithFormat:@"%s/controlmap/gamepads/%@", getenv("POJAV_HOME"), controlFile]; NSMutableDictionary *gamepadJSON = parseJSONFromFile(gamepadPath); gameMap = gamepadJSON[@"mGameMappingList"]; diff --git a/Natives/ios_uikit_bridge.m b/Natives/ios_uikit_bridge.m index e8d4003e..04d1022e 100644 --- a/Natives/ios_uikit_bridge.m +++ b/Natives/ios_uikit_bridge.m @@ -116,8 +116,7 @@ jstring UIKit_accessClipboard(JNIEnv* env, jint action, jstring copySrc) { void UIKit_launchMinecraftSurfaceVC() { // Leave this pref, might be useful later for launching with Quick Actions/Shortcuts/URL Scheme //setPreference(@"internal_launch_on_boot", getPreference(@"restart_before_launch")); - setPreference(@"selected_account", BaseAuthenticator.current.authData[@"username"]); - setPreference(@"internal_useStackQueue", @(isUseStackQueueCall ? YES : NO)); + setPrefObject(@"internal.selected_account", BaseAuthenticator.current.authData[@"username"]); dispatch_async(dispatch_get_main_queue(), ^{ UIWindow *window = currentWindow(); tmpRootVC = window.rootViewController; @@ -162,9 +161,12 @@ void UIKit_returnToSplitView() { } void launchInitialViewController(UIWindow *window) { - if ([getPreference(@"internal_launch_on_boot") boolValue]) { + window.rootViewController = [[LauncherSplitViewController alloc] initWithStyle:UISplitViewControllerStyleDoubleColumn]; +/* + if (getPrefBool(@"internal.internal_launch_on_boot")) { window.rootViewController = [[SurfaceViewController alloc] init]; } else { window.rootViewController = [[LauncherSplitViewController alloc] initWithStyle:UISplitViewControllerStyleDoubleColumn]; } +*/ } diff --git a/Natives/main.m b/Natives/main.m index e3f99af5..f5286d49 100644 --- a/Natives/main.m +++ b/Natives/main.m @@ -8,6 +8,7 @@ #import "AppDelegate.h" #import "customcontrols/CustomControlsUtils.h" #import "LauncherPreferences.h" +#import "PLProfiles.h" #import "SurfaceViewController.h" #import "config.h" @@ -166,7 +167,7 @@ void init_migrateToPlist(char* prefKey, char* filename) { } } - setPreference(@(prefKey), finalized); + setPrefObject(@(prefKey), finalized); [@"#README - this file has been merged into launcher_preferences.plist" writeToFile:path_str atomically:YES encoding:NSUTF8StringEncoding error:nil]; NSLog(@"[Pre-Init] File \"%s\" has been migrated", filename); } else { @@ -246,27 +247,11 @@ void init_setupCustomControls() { generateAndSaveDefaultControlForGamepad(); } -void init_setupLauncherProfiles() { - NSString *file = [@(getenv("POJAV_GAME_DIR")) stringByAppendingPathComponent:@"launcher_profiles.json"]; - if (![fm fileExistsAtPath:file]) { - NSDictionary *dict = @{ - @"profiles": @{ - @"(Default)": @{ - @"name": @"(Default)", - @"lastVersionId": @"latest-release" - } - }, - @"selectedProfile": @"(Default)" - }; - saveJSONToFile(dict, file); - } -} - void init_setupMultiDir() { - NSString *multidir = getPreference(@"game_directory"); + NSString *multidir = getPrefObject(@"general.game_directory"); if (multidir.length == 0) { multidir = @"default"; - setPreference(@"game_directory", multidir); + setPrefObject(@"general.game_directory", multidir); NSLog(@"[Pre-init] Game directory was not set. Defaulting to %@ for future use.\n", multidir); } else { NSLog(@"[Pre-init] Restored game directory preference (%@)\n", multidir); @@ -355,19 +340,18 @@ int main(int argc, char *argv[]) { init_hookUIKitConstructor(); loadPreferences(NO); - debugBoundsEnabled = [getPreference(@"debug_show_layout_bounds") boolValue]; - debugLogEnabled = [getPreference(@"debug_logging") boolValue]; + debugBoundsEnabled = getPrefBool(@"debug.debug_show_layout_bounds"); + debugLogEnabled = getPrefBool(@"general.debug_logging"); NSLog(@"[Debugging] Debug log enabled: %@", debugLogEnabled ? @"YES" : @"NO"); init_setupResolvConf(); init_setupMultiDir(); - init_setupLauncherProfiles(); + [PLProfiles updateCurrent]; init_setupAccounts(); init_setupCustomControls(); - init_migrateToPlist("selected_version", "config_ver.txt"); - init_migrateToPlist("java_args", "overrideargs.txt"); - init_migrateToPlist("env_variables", "custom_env.txt"); + init_migrateToPlist("java.java_args", "overrideargs.txt"); + init_migrateToPlist("java.env_variables", "custom_env.txt"); // If sandbox is disabled, W^X JIT can be enabled by PojavLauncher itself if (!isJITEnabled(true) && getEntitlementValue(@"com.apple.private.security.no-sandbox")) { diff --git a/Natives/resources/en.lproj/Localizable.strings b/Natives/resources/en.lproj/Localizable.strings index 33df9f1a..1ba9c6ec 100644 --- a/Natives/resources/en.lproj/Localizable.strings +++ b/Natives/resources/en.lproj/Localizable.strings @@ -116,6 +116,7 @@ "login.msa.progress.checkMCProfile" = "(5/5) Checking the Minecraft profile"; "News" = "News"; +"Profiles" = "Profiles"; "Settings" = "Settings"; "preference.section.general" = "General Settings"; @@ -254,6 +255,18 @@ "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."; +"preference.profile.title.name" = "Name"; +"preference.profile.title.version" = "Version"; +"preference.profile.title.default_touch_control" = "Touch controls"; +"preference.profile.title.default_gamepad_control" = "Gamepad controls"; + +"profile.error.name_exists" = "A profile with that name already exists. Please use another name."; +"profile.section.instance" = "Game Instance settings"; +"profile.section.profiles" = "Profiles in this instance"; +"profile.title.create" = "Create new profile"; +"profile.title.separate_preference" = "Isolate settings"; +"profile.detail.separate_preference" = "Enable this option to isolate any changes made in settings. Doing a Reset will restore to global settings instead of default settings."; + "controller_configurator.title.current" = "Gamepad configuration file"; "controller_configurator.title.type.xbox" = "Xbox"; "controller_configurator.title.type.playstation" = "PlayStation";