Files
PojavLauncher_iOS/Natives/installer/FabricInstallViewController.m
khanhduytran0 4c075f6941 [WIP] Add modpack installation
Added Modrinth, Fabric/Quilt automated installation
2024-02-14 10:16:13 +07:00

214 lines
9.0 KiB
Objective-C

#import "AFNetworking.h"
#import "FabricInstallViewController.h"
#import "FabricUtils.h"
#import "LauncherNavigationController.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 __typeof(self) 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 type) {
[weakSelf changeVersionTypeTo:type];
}
},
@{@"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 vendor){
[weakSelf fetchVersionEndpoints:vendor];
}
},
@{@"key": @"loaderType",
@"icon": @"ladybug",
@"title": @"preference.profile.title.loader_type",
@"type": typePickSegment,
@"pickList": @[localize(@"Release", nil), @"Unstable"],
//localize(@"Unstable", nil)
@"action": ^(int type) {
[weakSelf changeLoaderTypeTo:type];
}
},
@{@"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 = FabricUtils.endpoints;
[self fetchVersionEndpoints:0];
}
- (void)fetchVersionEndpoints:(int)type {
// Fetch version
__block BOOL errorShown = NO;
id errorCallback = ^(NSURLSessionTask *operation, NSError *error) {
if (!errorShown) {
errorShown = YES;
NSDebugLog(@"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) {
NSDebugLog(@"[%@ 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) {
NSDebugLog(@"[%@ Installer] Got %d loader versions", self.localKVO[@"loaderVendor"], response.count);
self.loaderMetadata = response;
[self changeLoaderTypeTo:[self.localKVO[@"loaderType_index"] intValue]];
} failure:errorCallback];
}
- (void)actionClose {
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
- (void)actionDone:(UIBarButtonItem *)sender {
sender.enabled = NO;
NSDictionary *endpoint = self.endpoints[self.localKVO[@"loaderVendor"]];
NSString *path = [NSString stringWithFormat:endpoint[@"json"], self.localKVO[@"gameVersion"], self.localKVO[@"loaderVersion"]];
NSDebugLog(@"[%@ Installer] Downloading %@", self.localKVO[@"loaderVendor"], path);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:path parameters:nil headers:nil progress:nil success:^(NSURLSessionTask *task, NSDictionary *response) {
sender.enabled = YES;
NSString *jsonPath = [NSString stringWithFormat:@"%1$s/versions/%2$@/%[email protected]", 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 {
[localVersionList addObject:@{
@"id": response[@"id"],
@"type": @"custom"}];
// Jump to the profile editor
LauncherProfileEditorViewController *vc = [LauncherProfileEditorViewController new];
vc.profile = @{
@"icon": endpoint[@"icon"],
@"name": response[@"id"],
@"lastVersionId": response[@"id"]
}.mutableCopy;
[self.navigationController pushViewController:vc animated:YES];
}
} failure:^(NSURLSessionTask *operation, NSError *error) {
sender.enabled = YES;
NSDebugLog(@"Error: %@", error);
showDialog(localize(@"Error", nil), error.localizedDescription);
}];
}
- (void)changeTypeToStable:(BOOL)stable forList:(NSMutableArray *)list fromMetadata:(NSArray *)metadata atRow:(int)row key:(NSString *)key {
[list removeAllObjects];
for (NSDictionary *version in metadata) {
if (version[@"stable"]) {
// Fabric: has stable key
if ([version[@"stable"] boolValue] != stable) continue;
} else {
// Quilt: has beta in the version name
if ([version[@"version"] containsString:@"beta"] == stable) continue;
}
[list addObject:version[@"version"]];
}
self.localKVO[key] = list.firstObject;
[self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:row inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
}
- (void)changeLoaderTypeTo:(int)type {
[self changeTypeToStable:type==0 forList:self.loaderList fromMetadata:self.loaderMetadata atRow:4 key:@"loaderVersion"];
}
- (void)changeVersionTypeTo:(int)type {
[self changeTypeToStable:type==0 forList:self.versionList fromMetadata:self.versionMetadata atRow:1 key:@"gameVersion"];
}
- (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