Skip to content

Commit

Permalink
Feat: built-in Fabric & Quilt installer
Browse files Browse the repository at this point in the history
  • Loading branch information
khanhduytran0 committed Aug 27, 2023
1 parent 921babe commit 9328f44
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 11 deletions.
3 changes: 3 additions & 0 deletions Natives/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GLOBAL_LDFLAGS}")
endif()

include_directories(
"."
"external/AFNetworking/AFNetworking"
"external/AFNetworking/UIKit+AFNetworking"
"external/DBNumberedSlider/Classes"
Expand Down Expand Up @@ -123,6 +124,8 @@ add_executable(PojavLauncher
external/ballpa1n/HostManager.c
external/ballpa1n/wrapped/HostManagerBridge.m

installer/FabricInstallViewController.m

input/ControllerInput.m
input/GyroInput.m
input/KeyboardInput.m
Expand Down
30 changes: 21 additions & 9 deletions Natives/LauncherProfilesViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//#import "NSFileManager+NRFileManager.h"
#import "PLProfiles.h"
#import "UIKit+hook.h"
#import "installer/FabricInstallViewController.h"
#import "ios_uikit_bridge.h"
#import "utils.h"

Expand All @@ -32,25 +33,27 @@ - (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],
identifier:@"vanilla" handler:^(UIAction *action) {
[self actionEditProfile:@{
@"name": @"",
@"lastVersionId": @"latest-release"}];
}],
#if 0 // TODO
[UIAction
actionWithTitle:@"OptiFine" image:nil
identifier:@"optifine" handler:createHandler],
#endif
[UIAction
actionWithTitle:@"Fabric" image:nil
identifier:@"fabric" handler:createHandler],
actionWithTitle:@"Fabric/Quilt" image:nil
identifier:@"fabric_or_quilt" handler:^(UIAction *action) {
[self actionCreateFabricProfile];
}],
#if 0
[UIAction
actionWithTitle:@"Forge" image:nil
identifier:@"forge" handler:createHandler]
Expand Down Expand Up @@ -81,9 +84,18 @@ - (void)actionTogglePrefIsolation:(UISwitch *)sender {
toggleIsolatedPref(sender.isOn);
}

- (void)actionCreateFabricProfile {
FabricInstallViewController *vc = [FabricInstallViewController new];
[self presentNavigatedViewController:vc];
}

- (void)actionEditProfile:(NSDictionary *)profile {
LauncherProfileEditorViewController *vc = [LauncherProfileEditorViewController new];
vc.profile = profile.mutableCopy;
[self presentNavigatedViewController:vc];
}

- (void)presentNavigatedViewController:(UIViewController *)vc {
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
nav.navigationBar.prefersLargeTitles = YES;
nav.modalInPresentation = YES;
Expand Down
5 changes: 5 additions & 0 deletions Natives/installer/FabricInstallViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <UIKit/UIKit.h>
#import "PLPrefTableViewController.h"

@interface FabricInstallViewController : PLPrefTableViewController
@end
188 changes: 188 additions & 0 deletions Natives/installer/FabricInstallViewController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#import "AFNetworking.h"
#import "FabricInstallViewController.h"
#import "LauncherPreferences.h"
#import "LauncherProfileEditorViewController.h"
#import "PickTextField.h"
#import "PLProfiles.h"
#import "ios_uikit_bridge.h"
#import "utils.h"
#include <objc/runtime.h>

@interface FabricInstallViewController()
@property(nonatomic) NSDictionary *endpoints;
@property(nonatomic) NSMutableDictionary *localKVO;
// Loader metadata
@property(nonatomic) NSArray<NSDictionary *> *loaderMetadata;
@property(nonatomic) NSMutableArray<NSString *> *loaderList;
// Game metadata
@property(nonatomic) NSArray<NSDictionary *> *versionMetadata;
@property(nonatomic) NSMutableArray<NSString *> *versionList;
@end

@implementation FabricInstallViewController

- (void)viewDidLoad {
// Setup navigation bar
self.title = localize(@"profile.title.install_fabric_quilt", 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 FabricInstallViewController *weakSelf = self;
self.localKVO = @{
@"gameVersion": @"1.20.1",
@"loaderVendor": @"Fabric",
@"loaderVersion": @"0.14.22"
}.mutableCopy;
self.getPreference = ^id(NSString *section, NSString *key){
return weakSelf.localKVO[key];
};
self.setPreference = ^(NSString *section, NSString *key, NSString *value){
weakSelf.localKVO[key] = value;
};

id typePickSegment = ^void(UITableViewCell *cell, NSString *section, NSString *key, NSDictionary *item) {
UISegmentedControl *view = [[UISegmentedControl alloc] initWithItems:item[@"pickList"]];
[view addTarget:weakSelf action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
if (view.selectedSegmentIndex == UISegmentedControlNoSegment) {
view.selectedSegmentIndex = 0;
}
cell.accessoryView = view;
};

self.versionList = [NSMutableArray new];
self.loaderList = [NSMutableArray new];
self.prefContents = @[
@[
@{@"key": @"gameType",
@"icon": @"ladybug",
@"title": @"preference.profile.title.version_type",
@"type": typePickSegment,
@"pickList": @[localize(@"Release", nil), localize(@"Snapshot", nil)],
@"action": ^(int selected) {
[weakSelf changeVersionTypeTo:selected];
}
},
@{@"key": @"gameVersion",
@"icon": @"archivebox",
@"title": @"preference.profile.title.version",
@"type": self.typePickField,
@"pickKeys": self.versionList,
@"pickList": self.versionList
},
@{@"key": @"loaderVendor",
@"icon": @"folder.badge.gearshape",
@"title": @"preference.profile.title.loader_vendor",
@"type": typePickSegment,
@"pickList": @[@"Fabric", @"Quilt"],
@"action": ^(int selected){
[weakSelf fetchVersionEndpoints:selected];
}
},
@{@"key": @"loaderVersion",
@"icon": @"doc.badge.gearshape",
@"title": @"preference.profile.title.loader_version",
@"type": self.typePickField,
@"pickKeys": self.loaderList,
@"pickList": self.loaderList
}
]
];

// Ensure views are loaded here
[super viewDidLoad];

// Init endpoint info
self.endpoints = @{
@"Fabric": @{
@"game": @"https://meta.fabricmc.net/v2/versions/game",
@"loader": @"https://meta.fabricmc.net/v2/versions/loader",
@"json": @"https://meta.fabricmc.net/v2/versions/loader/%@/%@/profile/json"
},
@"Quilt": @{
@"game": @"https://meta.quiltmc.org/v3/versions/game",
@"loader": @"https://meta.quiltmc.org/v3/versions/loader",
@"json": @"https://meta.quiltmc.org/v3/versions/loader/%@/%@/profile/json"
}
};
[self fetchVersionEndpoints:0];
}

- (void)fetchVersionEndpoints:(int)type {
// Fetch version
__block BOOL errorShown = NO;
id errorCallback = ^(NSURLSessionTask *operation, NSError *error) {
if (!errorShown) {
errorShown = YES;
NSLog(@"Error: %@", error);
showDialog(localize(@"Error", nil), error.localizedDescription);
[self actionClose];
}
};
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *endpoint = self.endpoints[self.localKVO[@"loaderVendor"]];
[manager GET:endpoint[@"game"] parameters:nil headers:nil progress:nil success:^(NSURLSessionTask *task, NSArray *response) {
NSLog(@"[%@ Installer] Got %d game versions", self.localKVO[@"loaderVendor"], response.count);
self.versionMetadata = response;
[self changeVersionTypeTo:[self.localKVO[@"gameType_index"] intValue]];
} failure:errorCallback];
[manager GET:endpoint[@"loader"] parameters:nil headers:nil progress:nil success:^(NSURLSessionTask *task, NSArray *response) {
NSLog(@"[%@ Installer] Got %d loader versions", self.localKVO[@"loaderVendor"], response.count);
self.loaderMetadata = response;
[self.loaderList removeAllObjects];
[self.loaderList addObjectsFromArray:[response valueForKey:@"version"]];
} failure:errorCallback];
}

- (void)actionClose {
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}

- (void)actionDone {
NSDictionary *endpoint = self.endpoints[self.localKVO[@"loaderVendor"]];
NSString *path = [NSString stringWithFormat:endpoint[@"json"], self.localKVO[@"gameVersion"], self.localKVO[@"loaderVersion"]];
NSLog(@"[%@ Installer] Downloading %@", self.localKVO[@"loaderVendor"], path);

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:path parameters:nil headers:nil progress:nil success:^(NSURLSessionTask *task, NSDictionary *response) {
NSString *jsonPath = [NSString stringWithFormat:@"%1$s/versions/%2$@/%2$@.json", getenv("POJAV_GAME_DIR"), response[@"id"]];
[NSFileManager.defaultManager createDirectoryAtPath:jsonPath.stringByDeletingLastPathComponent withIntermediateDirectories:YES attributes:nil error:nil];
NSError *error = saveJSONToFile(response, jsonPath);
if (error) {
showDialog(localize(@"Error", nil), error.localizedDescription);
} else {
// Jump to the profile editor
LauncherProfileEditorViewController *vc = [LauncherProfileEditorViewController new];
vc.profile = @{
@"name": response[@"id"],
@"lastVersionId": response[@"id"]
}.mutableCopy;
[self.navigationController pushViewController:vc animated:YES];
}
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(@"Error: %@", error);
showDialog(localize(@"Error", nil), error.localizedDescription);
}];
}

- (void)changeVersionTypeTo:(int)type {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"stable == %@", @(type == 0)];
NSArray *filteredList = [self.versionMetadata filteredArrayUsingPredicate:predicate];
[self.versionList removeAllObjects];
[self.versionList addObjectsFromArray:[filteredList valueForKey:@"version"]];
}

- (void)segmentChanged:(UISegmentedControl *)sender {
NSDictionary *item = objc_getAssociatedObject(sender, @"item");
self.localKVO[item[@"key"]] = [sender titleForSegmentAtIndex:sender.selectedSegmentIndex];
self.localKVO[[item[@"key"] stringByAppendingString:@"_index"]] = @(sender.selectedSegmentIndex);
void(^invokeAction)(int selected) = item[@"action"];
if (invokeAction) {
invokeAction(sender.selectedSegmentIndex);
}
}

@end
2 changes: 0 additions & 2 deletions Natives/ios_uikit_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
UIViewController* tmpRootVC;

void showDialog(NSString* title, NSString* message);
UIAlertController* createLoadingAlert(NSString* message);
jstring UIKit_accessClipboard(JNIEnv* env, jint action, jstring copySrc);
void UIKit_updateProgress(float progress, const char* message);
void UIKit_launchMinecraftSurfaceVC();
void UIKit_returnToSplitView();
void launchInitialViewController(UIWindow *window);
4 changes: 4 additions & 0 deletions Natives/resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,10 @@
"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.loader_vendor" = "Loader vendor";
"preference.profile.title.loader_version" = "Loader version";
"preference.profile.title.version" = "Version";
"preference.profile.title.version_type" = "Version type";
"preference.profile.title.default_touch_control" = "Touch controls";
"preference.profile.title.default_gamepad_control" = "Gamepad controls";

Expand All @@ -268,6 +271,7 @@
"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.";
"profile.title.install_fabric_quilt" = "Install Fabric/Quilt";

"controller_configurator.title.current" = "Gamepad configuration file";
"controller_configurator.title.type.xbox" = "Xbox";
Expand Down

0 comments on commit 9328f44

Please sign in to comment.