public async Task <ScriptInstallStatus> InstallScript(ContentType contentType, FGORegion region, List <string> installPaths, string baseInstallPath, int installId, string assetStorageCheck = null, ObservableCollection <InstallerPage.TranslationGUIObject> translationGuiObjects = null) { _cm.ClearCache(); var gui = translationGuiObjects != null; var guiObject = gui ? translationGuiObjects.First(w => w.BundleID == installId) : null; if (guiObject != null) { guiObject.Status = UIFunctions.GetResourceString("InstallingFetchingHandshake"); guiObject.ButtonInstallText = UIFunctions.GetResourceString("InstallingText"); guiObject.TextColor = Color.Chocolate; } // fetch new translation list to ensure it is up to date var restful = new RestfulAPI(); var translationList = await restful.GetHandshakeApiResponse(region, assetStorageCheck); if (!translationList.IsSuccessful || translationList.Data.Status != 200) { return(new ScriptInstallStatus() { IsSuccessful = false, ErrorMessage = String.Format(UIFunctions.GetResourceString("InstallHandshakeFailure"), translationList.StatusCode, translationList.Data?.Message) }); } if (assetStorageCheck != null) { switch (translationList.Data.Response.AssetStatus) { case HandshakeAssetStatus.UpdateRequired: return(new ScriptInstallStatus() { IsSuccessful = false, ErrorMessage = String.Format("AssetStorage.txt out of date, skipping update.") }); default: break; } } // download required scripts var groupToInstall = translationList.Data.Response.Translations.FirstOrDefault(w => w.Group == installId); if (groupToInstall?.Scripts == null) { return(new ScriptInstallStatus() { IsSuccessful = false, ErrorMessage = String.Format(UIFunctions.GetResourceString("InstallMissingScriptFailure"), installId) }); } if (!gui && groupToInstall.Hidden) { return(new ScriptInstallStatus() { IsSuccessful = false, ErrorMessage = String.Format(UIFunctions.GetResourceString("InstallHiddenScriptFailure"), installId) }); } var totalScripts = groupToInstall.Scripts.Count; if (guiObject != null) { guiObject.Status = String.Format(UIFunctions.GetResourceString("InstallDownloadingScripts"), 1, totalScripts); } ConcurrentDictionary <string, byte[]> scriptDictionary = new ConcurrentDictionary <string, byte[]>(); object lockObj = new Object(); try { await groupToInstall.Scripts.ParallelForEachAsync(async script => { var name = script.Key; var downloadUrl = script.Value.DownloadURL; var downloadResponse = await restful.GetScript(downloadUrl); var downloadedScript = downloadResponse.RawBytes; if (!downloadResponse.IsSuccessful) { throw new EndEarlyException(String.Format(UIFunctions.GetResourceString("InstallScriptDownloadFailure"), installId, downloadUrl, downloadResponse.ErrorMessage)); } if (downloadedScript == null) { throw new EndEarlyException(String.Format(UIFunctions.GetResourceString("InstallEmptyFileFailure"), installId, downloadUrl)); } var scriptSha = ScriptUtil.Sha1(downloadedScript); if (scriptSha != script.Value.TranslatedSHA1) { throw new EndEarlyException(String.Format(UIFunctions.GetResourceString("InstallChecksumFailure"), installId, downloadedScript.Length, downloadUrl)); } scriptDictionary.TryAdd(name, downloadedScript); lock (lockObj) { if (guiObject != null) { guiObject.Status = String.Format(UIFunctions.GetResourceString("InstallDownloadingScripts"), Math.Min(scriptDictionary.Count, totalScripts), totalScripts); } } }, maxDegreeOfParallelism : 4); } catch (EndEarlyException ex) { return(new ScriptInstallStatus() { IsSuccessful = false, ErrorMessage = ex.ToString() }); } if (guiObject != null) { guiObject.Status = String.Format(UIFunctions.GetResourceString("InstallDownloadNewAssetStorage")); } Dictionary <string, byte[]> newAssetStorages = new Dictionary <string, byte[]>(); // get new assetstorage.txt foreach (var game in installPaths) { var assetStoragePath = contentType != ContentType.DirectAccess ? $"data/{game}/files/data/d713/{InstallerPage._assetList}" : $"{game}/files/data/d713/{InstallerPage._assetList}"; var fileContents = await _cm.GetFileContents(contentType, assetStoragePath, baseInstallPath); if (!fileContents.Successful || fileContents.FileContents.Length == 0) { return(new ScriptInstallStatus() { IsSuccessful = false, ErrorMessage = String.Format(UIFunctions.GetResourceString("InstallEmptyAssetStorage"), installId, assetStoragePath, fileContents.Error) }); } // remove bom var base64 = ""; await using var inputStream = new MemoryStream(fileContents.FileContents); using (var reader = new StreamReader(inputStream, Encoding.ASCII)) { base64 = await reader.ReadToEndAsync(); } var newAssetList = await restful.SendAssetList(base64, installId, region); if (!newAssetList.IsSuccessful) { return(new ScriptInstallStatus() { IsSuccessful = false, ErrorMessage = string.Format(UIFunctions.GetResourceString("InstallAssetStorageAPIFailure"), installId, newAssetList.StatusCode, newAssetList.Data?.Message) }); } // add bom await using var outputStream = new MemoryStream(); await using (var writer = new StreamWriter(outputStream, Encoding.ASCII)) { await writer.WriteAsync(newAssetList.Data.Response["data"]); } newAssetStorages.Add(assetStoragePath, outputStream.ToArray()); } // prepare files Dictionary <string, byte[]> filesToWrite = newAssetStorages; foreach (var game in installPaths) { foreach (var asset in scriptDictionary) { var assetInstallPath = contentType != ContentType.DirectAccess ? $"data/{game}/files/data/d713/{asset.Key}" : $"{game}/files/data/d713/{asset.Key}"; filesToWrite.Add(assetInstallPath, asset.Value); } } // write files int j = 1; int tot = filesToWrite.Count; foreach (var file in filesToWrite) { if (file.Key.EndsWith(".bin")) { await _cm.RemoveFileIfExists(contentType, file.Key, baseInstallPath); } } _cm.ClearCache(); foreach (var file in filesToWrite) { if (guiObject != null) { guiObject.Status = String.Format(UIFunctions.GetResourceString("InstallWriteFile"), j, tot); } await _cm.WriteFileContents(contentType, file.Key, baseInstallPath, file.Value); j += 1; } if (guiObject != null) { guiObject.Status = String.Format(UIFunctions.GetResourceString("InstallFinished")); guiObject.TextColor = Color.LimeGreen; } Preferences.Set($"InstalledScript_{region}", JsonConvert.SerializeObject(groupToInstall)); return(new ScriptInstallStatus() { IsSuccessful = true, ErrorMessage = "" }); }
protected override async void OnActivityResult(int requestCode, Result resultCode, Intent intent) { base.OnActivityResult(requestCode, resultCode, intent); Log.Info("TranslateFGO", $"ActivityResult Code: {resultCode}, Result: {resultCode}, Data: {intent?.DataString}"); if (resultCode == Result.Ok && requestCode == (int)RequestCodes.FolderIntentRequestCode) { var service = new ContentManager(); var uri = intent.Data; var selectedFolder = DocumentsContract.GetTreeDocumentId(uri); if (selectedFolder == null || !selectedFolder.EndsWith("Android")) { var folderSplit = selectedFolder?.Split(":").Last(); var infoText = UIFunctions.GetResourceString("AndroidFolderNotSelected"); var errorMessage = String.Format(infoText, !string.IsNullOrEmpty(folderSplit) ? folderSplit : "none"); Toast.MakeText(this.Context, errorMessage, ToastLength.Long)?.Show(); return; } service.ClearCache(); var dataChildren = service.GetFolderChildren(uri, "data/"); // Get list of children to find FGO folders if (dataChildren.Count == 0) { var infoText = UIFunctions.GetResourceString("AndroidDataFolderEmpty"); Toast.MakeText(this.Context, infoText, ToastLength.Long)?.Show(); return; } var appNamesList = ContentManager.ValidAppNames.ToList(); bool found = false; foreach (var folder in dataChildren) { if (appNamesList.Contains(folder.Path.Split("/").Last())) { found = true; } } if (!found) { var infoText = UIFunctions.GetResourceString("NoFGOInstallationFoundToast"); Toast.MakeText(this.Context, infoText, ToastLength.Long)?.Show(); return; } // Save URL try { this.ContentResolver.TakePersistableUriPermission(uri, intent.Flags & (ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission)); } catch (Exception ex) { Log.Error("BetterFGO", $"Error thrown while persisting uri: {ex}"); var errorText = UIFunctions.GetResourceString("UnknownError"); var errorMessage = String.Format(errorText, ex.ToString()); Toast.MakeText(this.Context, errorMessage, ToastLength.Long)?.Show(); } // If we got this far, all is well var successText = UIFunctions.GetResourceString("Android11SetupSuccessful"); Toast.MakeText(this.Context, successText, ToastLength.Long)?.Show(); Log.Info("BetterFGO", $"Saving URI: {uri?.ToString()}"); Preferences.Set("StorageType", (int)ContentType.StorageFramework); Preferences.Set("StorageLocation", uri?.ToString()); MessagingCenter.Send(Xamarin.Forms.Application.Current, "return_to_main_page"); } }