#import "AFNetworking.h" #import "ForgeInstallViewController.h" #import "LauncherNavigationController.h" #import "WFWorkflowProgressView.h" #import "ios_uikit_bridge.h" #import "utils.h" #include @interface ForgeInstallViewController() @property(atomic) AFURLSessionManager *afManager; @property(nonatomic) WFWorkflowProgressView *progressView; @property(nonatomic) NSDictionary *endpoints; @property(nonatomic) NSMutableArray *visibilityList; @property(nonatomic) NSMutableArray *versionList; @property(nonatomic) NSMutableArray *forgeList; @property(nonatomic, assign) BOOL isVersionElement; @end @implementation ForgeInstallViewController - (void)viewDidLoad { [super viewDidLoad]; UISegmentedControl *segment = [[UISegmentedControl alloc] initWithItems:@[@"Forge", @"NeoForge"]]; segment.selectedSegmentIndex = 0; [segment addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged]; self.navigationItem.titleView = segment; // Load WFWorkflowProgressView dlopen("/System/Library/PrivateFrameworks/WorkflowUIServices.framework/WorkflowUIServices", RTLD_GLOBAL); self.progressView = [[NSClassFromString(@"WFWorkflowProgressView") alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; self.progressView.resolvedTintColor = self.view.tintColor; [self.progressView addTarget:self action:@selector(actionCancelDownload) forControlEvents:UIControlEventTouchUpInside]; self.endpoints = @{ @"Forge": @{ @"installer": @"https://maven.minecraftforge.net/net/minecraftforge/forge/%1$@/forge-%1$@-installer.jar", @"metadata": @"https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml" }, @"NeoForge": @{ @"installer": @"https://maven.neoforged.net/net/neoforged/forge/%1$@/forge-%1$@-installer.jar", @"metadata": @"https://maven.neoforged.net/releases/net/neoforged/forge/maven-metadata.xml" } }; self.visibilityList = [NSMutableArray new]; self.versionList = [NSMutableArray new]; self.forgeList = [NSMutableArray new]; [self loadMetadataFromVendor:@"Forge"]; } - (void)actionCancelDownload { [self.afManager invalidateSessionCancelingTasks:YES resetSession:NO]; } - (void)actionClose { [self.navigationController dismissViewControllerAnimated:YES completion:nil]; } - (void)loadMetadataFromVendor:(NSString *)vendor { [self switchToLoadingState]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL *url = [[NSURL alloc] initWithString:self.endpoints[vendor][@"metadata"]]; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url]; parser.delegate = self; if (![parser parse]) { dispatch_async(dispatch_get_main_queue(), ^{ showDialog(localize(@"Error", nil), parser.parserError.localizedDescription); [self actionClose]; }); } }); } - (void)switchToLoadingState { UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:indicator]; [indicator startAnimating]; self.navigationController.modalInPresentation = YES; } - (void)switchToReadyState { UIActivityIndicatorView *indicator = (id)self.navigationItem.rightBarButtonItem.customView; [indicator stopAnimating]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemClose target:self action:@selector(actionClose)]; self.navigationController.modalInPresentation = NO; } - (void)segmentChanged:(UISegmentedControl *)segment { [self.visibilityList removeAllObjects]; [self.versionList removeAllObjects]; [self.forgeList removeAllObjects]; [self.tableView reloadData]; NSString *vendor = [segment titleForSegmentAtIndex:segment.selectedSegmentIndex]; [self loadMetadataFromVendor:vendor]; } #pragma mark UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return self.versionList.count; } - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { UITableViewHeaderFooterView *view = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:@"section"]; if (!view) { view = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:@"section"]; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tableViewDidSelectSection:)]; [view addGestureRecognizer:tapGesture]; } return view; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return self.versionList[section]; } - (void)tableViewDidSelectSection:(UITapGestureRecognizer *)sender { UITableViewHeaderFooterView *view = (id)sender.view; int section = [self.versionList indexOfObject:view.textLabel.text]; self.visibilityList[section] = @(!self.visibilityList[section].boolValue); [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationAutomatic]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.visibilityList[section].boolValue ? self.forgeList[section].count : 0; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; } cell.textLabel.text = self.forgeList[indexPath.section][indexPath.row]; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; tableView.allowsSelection = NO; [self switchToLoadingState]; self.progressView.fractionCompleted = 0; UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.accessoryView = self.progressView; UISegmentedControl *segment = (id)self.navigationItem.titleView; NSString *vendor = [segment titleForSegmentAtIndex:segment.selectedSegmentIndex]; NSString *jarURL = [NSString stringWithFormat:self.endpoints[vendor][@"installer"], cell.textLabel.text]; NSString *outPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.jar"]; NSDebugLog(@"[Forge Installer] Downloading %@", jarURL); self.afManager = [AFURLSessionManager new]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:jarURL]]; NSURLSessionDownloadTask *downloadTask = [self.afManager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull progress){ dispatch_async(dispatch_get_main_queue(), ^{ self.progressView.fractionCompleted = progress.fractionCompleted; }); } destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) { [NSFileManager.defaultManager removeItemAtPath:outPath error:nil]; return [NSURL fileURLWithPath:outPath]; } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ tableView.allowsSelection = YES; cell.accessoryView = nil; if (error) { if (error.code != NSURLErrorCancelled) { NSDebugLog(@"Error: %@", error); showDialog(localize(@"Error", nil), error.localizedDescription); } [self switchToReadyState]; return; } LauncherNavigationController *navVC = (id)((UISplitViewController *)self.presentingViewController).viewControllers[1]; [self dismissViewControllerAnimated:YES completion:^{ [navVC enterModInstallerWithPath:outPath hitEnterAfterWindowShown:YES]; }]; }); }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [downloadTask resume]; }); } - (void)addVersionToList:(NSString *)version { if (![version containsString:@"-"]) { return; } NSRange range = [version rangeOfString:@"-"]; NSString *gameVersion = [version substringToIndex:range.location]; //NSString *forgeVersion = [version substringFromIndex:range.location + 1]; if (![self.versionList containsObject:gameVersion]) { [self.visibilityList addObject:@(NO)]; [self.versionList addObject:gameVersion]; [self.forgeList addObject:[NSMutableArray new]]; } [self.forgeList.lastObject addObject:version]; } #pragma mark NSXMLParser - (void)parserDidEndDocument:(NSXMLParser *)unused { dispatch_async(dispatch_get_main_queue(), ^{ [self switchToReadyState]; [self.tableView reloadData]; }); } - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict { self.isVersionElement = [elementName isEqualToString:@"version"]; } - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)version { if (self.isVersionElement) { [self addVersionToList:version]; } } @end