#import #import "authenticator/BaseAuthenticator.h" #import "AccountListViewController.h" #import "AFNetworking.h" #import "LauncherPreferences.h" #import "UIImageView+AFNetworking.h" #import "ios_uikit_bridge.h" #import "utils.h" @interface ATableViewCell : UITableViewCell @end @implementation ATableViewCell @end @interface AccountListViewController() @property(nonatomic, strong) NSMutableArray *accountList; @property(nonatomic) ASWebAuthenticationSession *authVC; @end @implementation AccountListViewController - (void)viewDidLoad { [super viewDidLoad]; if (self.accountList == nil) { self.accountList = [NSMutableArray array]; } else { [self.accountList removeAllObjects]; } // List accounts NSString *listPath = [NSString stringWithFormat:@"%s/accounts", getenv("POJAV_HOME")]; NSFileManager *fm = [NSFileManager defaultManager]; NSArray *files = [fm contentsOfDirectoryAtPath:listPath error:nil]; for(NSString *file in files) { NSString *path = [listPath stringByAppendingPathComponent:file]; BOOL isDir = NO; [fm fileExistsAtPath:path isDirectory:(&isDir)]; if(!isDir && [file hasSuffix:@".json"]) { [self.accountList addObject:parseJSONFromFile(path)]; } } [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.accountList.count + 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; } if (indexPath.row == self.accountList.count) { cell.imageView.image = [UIImage imageNamed:@"IconAdd"]; cell.textLabel.text = localize(@"login.option.add", nil); return cell; } NSDictionary *selected = self.accountList[indexPath.row]; // By default, display the saved username cell.textLabel.text = selected[@"username"]; if ([selected[@"username"] hasPrefix:@"Demo."]) { // Remove the prefix "Demo." cell.textLabel.text = [selected[@"username"] substringFromIndex:5]; cell.detailTextLabel.text = localize(@"login.option.demo", nil); } else if (selected[@"xboxGamertag"] == nil) { cell.detailTextLabel.text = localize(@"login.option.local", nil); } else { // Display the Xbox gamertag for online accounts cell.detailTextLabel.text = selected[@"xboxGamertag"]; } cell.imageView.contentMode = UIViewContentModeCenter; [cell.imageView setImageWithURL:[NSURL URLWithString:[selected[@"profilePicURL"] stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]] placeholderImage:[UIImage imageNamed:@"DefaultAccount"]]; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; if (indexPath.row == self.accountList.count) { [self actionAddAccount:cell]; return; } if (@available(iOS 13.0, *)) { self.modalInPresentation = YES; } self.tableView.userInteractionEnabled = NO; [self addActivityIndicatorTo:cell]; id callback = ^(NSString* status, BOOL success) { [self callbackMicrosoftAuth:status success:success forCell:cell]; }; [[BaseAuthenticator loadSavedName:self.accountList[indexPath.row][@"username"]] refreshTokenWithCallback:callback]; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // TODO: invalidate token NSString *str = self.accountList[indexPath.row][@"username"]; NSFileManager *fm = [NSFileManager defaultManager]; NSString *path = [NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), str]; if (self.whenDelete != nil) { self.whenDelete(str); } [fm removeItemAtPath:path error:nil]; [self.accountList removeObjectAtIndex:indexPath.row]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; } } - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row == self.accountList.count) { return UITableViewCellEditingStyleNone; } else { return UITableViewCellEditingStyleDelete; } } - (void)actionAddAccount:(UITableViewCell *)sender { UIAlertController *picker = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; UIAlertAction *actionMicrosoft = [UIAlertAction actionWithTitle:localize(@"login.option.microsoft", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self actionLoginMicrosoft:sender]; }]; [picker addAction:actionMicrosoft]; UIAlertAction *actionLocal = [UIAlertAction actionWithTitle:localize(@"login.option.local", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self actionLoginLocal:sender]; }]; [picker addAction:actionLocal]; UIAlertAction *cancel = [UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil]; [picker addAction:cancel]; picker.popoverPresentationController.sourceView = sender; picker.popoverPresentationController.sourceRect = sender.bounds; [self presentViewController:picker animated:YES completion:nil]; } - (void)actionLoginLocal:(UIView *)sender { if ([getPreference(@"local_warn") boolValue] == YES) { setPreference(@"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; UIAlertAction *ok = [UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self actionLoginLocal:sender];}]; [alert addAction:ok]; [self presentViewController:alert animated:YES completion:nil]; return; } UIAlertController *controller = [UIAlertController alertControllerWithTitle:localize(@"Sign in", nil) message:localize(@"login.option.local", nil) preferredStyle:UIAlertControllerStyleAlert]; [controller addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.placeholder = localize(@"login.alert.field.username", nil); textField.clearButtonMode = UITextFieldViewModeWhileEditing; textField.borderStyle = UITextBorderStyleRoundedRect; }]; [controller addAction:[UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { NSArray *textFields = controller.textFields; UITextField *usernameField = textFields[0]; if (usernameField.text.length < 3 || usernameField.text.length > 16) { controller.message = localize(@"login.error.username.outOfRange", nil); [self presentViewController:controller animated:YES completion:nil]; } else { id callback = ^(NSString* status, BOOL success) { [self dismissViewControllerAnimated:YES completion:nil]; self.whenItemSelected(); }; [[[LocalAuthenticator alloc] initWithInput:usernameField.text] loginWithCallback:callback]; } }]]; [controller addAction:[UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:controller animated:YES completion:nil]; } - (void)actionLoginMicrosoft:(UITableViewCell *)sender { NSURL *url = [NSURL URLWithString:@"https://login.live.com/oauth20_authorize.srf?client_id=00000000402b5328&response_type=code&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL&redirect_url=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf"]; self.authVC = [[ASWebAuthenticationSession alloc] initWithURL:url callbackURLScheme:@"ms-xal-00000000402b5328" completionHandler:^(NSURL * _Nullable callbackURL, NSError * _Nullable error) { if (callbackURL == nil) { if (error.code != ASWebAuthenticationSessionErrorCodeCanceledLogin) { showDialog(self, localize(@"Error", nil), error.localizedDescription); } return; } NSString *urlString = [callbackURL absoluteString]; // NSLog(@"URL returned = %@", [callbackURL absoluteString]); if ([urlString containsString:@"/auth/?code="]) { if (@available(iOS 13.0, *)) { self.modalInPresentation = YES; } self.tableView.userInteractionEnabled = NO; [self addActivityIndicatorTo:sender]; NSArray *components = [urlString componentsSeparatedByString:@"/auth/?code="]; id callback = ^(NSString* status, BOOL success) { if ([status isEqualToString:@"DEMO"] && success) { showDialog(self, localize(@"login.warn.title.demomode", nil), localize(@"login.warn.message.demomode", nil)); } [self callbackMicrosoftAuth:status success:success forCell:sender]; }; [[[MicrosoftAuthenticator alloc] initWithInput:components[1]] loginWithCallback:callback]; } else { NSArray *components = [urlString componentsSeparatedByString:@"/auth/?error="]; if ([components[1] hasPrefix:@"access_denied"]) { // Ignore access denial responses return; } NSString *outError = [components[1] stringByReplacingOccurrencesOfString:@"&error_description=" withString:@": "]; outError = [outError stringByRemovingPercentEncoding]; showDialog(self, localize(@"Error", nil), outError); } }]; if (@available(iOS 13.0, *)) { self.authVC.prefersEphemeralWebBrowserSession = YES; self.authVC.presentationContextProvider = self; } if ([self.authVC start] == NO) { showDialog(self, localize(@"Error", nil), @"Unable to open Safari"); } } - (void)addActivityIndicatorTo:(UITableViewCell *)cell { UIActivityIndicatorViewStyle indicatorStyle; if (@available(iOS 13.0, *)) { indicatorStyle = UIActivityIndicatorViewStyleMedium; } else { indicatorStyle = UIActivityIndicatorViewStyleGray; } UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:indicatorStyle]; cell.accessoryView = indicator; [indicator sizeToFit]; [indicator startAnimating]; } - (void)removeActivityIndicatorFrom:(UITableViewCell *)cell { UIActivityIndicatorView *indicator = (id)cell.accessoryView; [indicator stopAnimating]; cell.accessoryView = nil; } - (void)callbackMicrosoftAuth:(NSString *)status success:(BOOL)success forCell:(UITableViewCell *)cell { if (status != nil) { cell.detailTextLabel.text = status; if (!success) { if (@available(iOS 13.0, *)) { self.modalInPresentation = NO; } self.tableView.userInteractionEnabled = YES; [self removeActivityIndicatorFrom:cell]; NSLog(@"[MSA] Error: %@", status); showDialog(self, localize(@"Error", nil), status); } } else if (success) { [self removeActivityIndicatorFrom:cell]; [self dismissViewControllerAnimated:YES completion:nil]; self.whenItemSelected(); } } #pragma mark - UIPopoverPresentationControllerDelegate - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection { return UIModalPresentationNone; } #pragma mark - ASWebAuthenticationPresentationContextProviding - (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session API_AVAILABLE(ios(13.0)){ return UIApplication.sharedApplication.windows.firstObject; } @end