public async Task <BaseAPIResponse> SendSuccess(FGORegion region, int language, TranslationInstallType installType, int groupId, bool success, string errorMessage, bool isAndroid11Install) { var request = new RestRequest("translate/result") { Method = Method.POST }; if (!Preferences.ContainsKey("InstallID")) { Preferences.Set("InstallID", Guid.NewGuid().ToString()); } request.AddHeader("Content-type", "application/json"); var sendObject = new Dictionary <string, object>() { { "region", (int)region }, { "successful", success }, { "error", errorMessage }, { "language", language }, { "group", groupId }, { "installType", (int)installType }, { "android11Install", isAndroid11Install }, { "guid", Preferences.Get("InstallID", "") } }; request.AddParameter("application/json; charset=utf-8", SimpleJson.SerializeObject(sendObject), ParameterType.RequestBody); return((await ExecuteAsync <BaseAPIResponse>(request)).Data); }
public async Task Uninstall(FGORegion region) { _cm.ClearCache(); bool answer = await DisplayAlert(AppResources.Warning, AppResources.UninstallWarning, AppResources.Yes, AppResources.No); if (!answer) { return; } SwitchButtons(false); HashSet <string> filesToRemove = new HashSet <string>(); try { foreach (var game in _installedFgoInstances.Where(w => w.Region == region).Select(s => s.Path)) { foreach (var basePath in from script in _translations from script2 in script.Value.Scripts select script2.Key into file select _android11Access ? $"data/{game}/files/data/d713/{file}" : $"{game}/files/data/d713/{file}") { filesToRemove.Add(basePath); } } if (filesToRemove.Count == 0) { return; } int i = 0; foreach (var file in filesToRemove) { i += 1; RevertButton.Text = String.Format(AppResources.UninstallingText, i, filesToRemove.Count); OnPropertyChanged(); await _cm.RemoveFileIfExists(_android11Access?ContentType.StorageFramework : ContentType.DirectAccess, file, _storageLocation); } RevertButton.Text = AppResources.UninstallFinished; OnPropertyChanged(); Preferences.Remove($"InstalledScript_{region}"); await Task.Delay(1000); } catch (Exception ex) { await DisplayAlert(AppResources.InternalError, String.Format(AppResources.InternalErrorDetails, ex.ToString()), AppResources.OK); } await UpdateTranslationList(); }
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 = "" }); }
public Task <bool> UninstallScripts(ContentType contentType, FGORegion region, List <string> installPaths, string baseInstallPath) { throw new System.NotImplementedException(); // implemented locally }
/// <summary> /// Get a handshake response /// </summary> /// <returns>Handshake API struct</returns> public async Task <IRestResponse <HandshakeAPIResponse> > GetHandshakeApiResponse(FGORegion region, string assetStorage = null) { string endpoint; if (Preferences.ContainsKey("AuthKey")) { endpoint = $"translate/scripts/{(int)region}/" + Preferences.Get("AuthKey", "") + "?_ts=" + DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } else { endpoint = $"translate/scripts/{(int)region}" + "?_ts=" + DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } var request = new RestRequest(endpoint) { Method = Method.POST }; if (!string.IsNullOrWhiteSpace(assetStorage)) { request.AddHeader("Content-type", "application/json"); var sendObject = new Dictionary <string, object>() { { "assetstorage", assetStorage } }; request.AddParameter("application/json; charset=utf-8", SimpleJson.SerializeObject(sendObject), ParameterType.RequestBody); } return(await ExecuteAsync <HandshakeAPIResponse>(request)); }
/// <summary> /// Get new asset list /// </summary> /// <param name="assetList">Existing asset list</param> /// <param name="bundleId">Bundle ID</param> /// <returns></returns> public async Task <IRestResponse <AssetListAPIResponse> > SendAssetList(string assetList, int bundleId, FGORegion region) { var request = new RestRequest("translate/update-asset-list") { Method = Method.POST }; request.AddHeader("Content-type", "application/json"); var sendObject = new Dictionary <string, object>() { { "data", assetList }, { "group", bundleId }, { "region", (int)region } }; if (Preferences.ContainsKey("AuthKey")) { sendObject.Add("key", Preferences.Get("AuthKey", "")); } request.AddParameter("application/json; charset=utf-8", SimpleJson.SerializeObject(sendObject), ParameterType.RequestBody); return(await ExecuteAsync <AssetListAPIResponse>(request)); }
public async Task Install(FGORegion region, int toInstall) { _cm.ClearCache(); bool answer = await DisplayAlert(AppResources.Warning, String.Format(AppResources.InstallWarning, _translations[toInstall].TotalSize.Bytes().Humanize("#.## MB")), AppResources.Yes, AppResources.No); if (!answer) { return; } SwitchButtons(false); var rest = new RestfulAPI(); var language = _handshake.Response.Translations.First(f => f.Group == toInstall).Language; Task successSendTask = null; try { var installResult = await _sm.InstallScript( _android11Access?ContentType.StorageFramework : ContentType.DirectAccess, region, _installedFgoInstances.Where(w => w.Region == region).Select(s => s.Path).ToList(), _storageLocation, toInstall, null, _guiObjects ); if (!installResult.IsSuccessful) { successSendTask = rest.SendSuccess(region, language, TranslationInstallType.Manual, toInstall, false, installResult.ErrorMessage, _android11Access); await DisplayAlert(AppResources.Error, installResult.ErrorMessage, AppResources.OK); } else { successSendTask = rest.SendSuccess(region, language, TranslationInstallType.Manual, toInstall, true, "", _android11Access); await Task.Delay(1000); } } catch (System.UnauthorizedAccessException ex) { if (!_android11Access) { successSendTask = rest.SendSuccess(region, language, TranslationInstallType.Manual, toInstall, false, "Unauthorized error handler: " + ex.ToString(), _android11Access); var retry = await DisplayAlert(AppResources.DirectoryPermissionDeniedTitle, AppResources.Android11AskToSetup, AppResources.Yes, AppResources.No); if (retry) { await successSendTask; MessagingCenter.Send(Xamarin.Forms.Application.Current, "installer_page_goto_pre_initialize"); return; } await successSendTask; return; } else { throw; } } catch (Exception ex) { successSendTask = rest.SendSuccess(region, language, TranslationInstallType.Manual, toInstall, false, ex.ToString(), _android11Access); await DisplayAlert(AppResources.InternalError, String.Format(AppResources.InternalErrorDetails, ex.ToString()), AppResources.OK); } await successSendTask; await UpdateTranslationList(); //SwitchButtons(true); }
/// <summary> /// Processes valid assets to know which buttons to display /// </summary> /// <returns></returns> public async Task ProcessAssets(ContentType storageType, string pathToCheckWith, string storageLocationBase, FGORegion region) { List <string> validSha = new List <string>(); _translations?.Clear(); _guiObjects?.Clear(); var installedScriptString = Preferences.Get($"InstalledScript_{region}", null); if (installedScriptString != null) { _installedBundle = JsonConvert.DeserializeObject <TranslationList>(installedScriptString); foreach (var scriptBundle in _installedBundle.Scripts) { validSha.Add(scriptBundle.Value.TranslatedSHA1); // Add existing } } else { _installedBundle = null; } foreach (var scriptBundleSet in _handshake.Response.Translations) { foreach (var scriptBundle in scriptBundleSet.Scripts) { validSha.Add(scriptBundle.Value.TranslatedSHA1); // Add all valid sha1s } } if (_handshake.Response.Translations.Count > 0) { foreach (var scriptBundleSet in _handshake.Response.Translations) { ConcurrentBag <Tuple <long, long> > results = new ConcurrentBag <Tuple <long, long> >(); await scriptBundleSet.Scripts.ParallelForEachAsync(async scriptBundle => { // Check hashes var filePath = Path.Combine(pathToCheckWith, scriptBundle.Key); var fileContentsResult = await _cm.GetFileContents(storageType, filePath, storageLocationBase); if (scriptBundle.Key.Contains('/') || scriptBundle.Key.Contains('\\')) // for security purposes, don't allow directory traversal { throw new FileNotFoundException(); } TranslationFileStatus status; var fileNotExists = fileContentsResult.Error == FileErrorCode.NotExists; if (!fileNotExists) { var sha1 = ScriptUtil.Sha1(fileContentsResult.FileContents); // SHA of file currently in use if (sha1 == scriptBundle.Value.GameSHA1) // Not modified { status = TranslationFileStatus.NotModified; } else if (sha1 == scriptBundle.Value.TranslatedSHA1) // English is installed { status = TranslationFileStatus.Translated; } else if (validSha.Contains(sha1) && (_installedBundle == null || (_installedBundle != null && scriptBundleSet.Group != _installedBundle.Group))) { status = TranslationFileStatus.DifferentTranslation; } else if (_installedBundle != null && scriptBundleSet.Group == _installedBundle.Group) { status = TranslationFileStatus.UpdateAvailable; } else { status = TranslationFileStatus.Invalid; } } else { status = TranslationFileStatus.Missing; } scriptBundle.Value.Status = status; results.Add(new Tuple <long, long>(scriptBundle.Value.LastModified, scriptBundle.Value.Size)); }, maxDegreeOfParallelism : 4); long lastModified = results.Max(m => m.Item1); long totalSize = results.Sum(s => s.Item2); scriptBundleSet.TotalSize = totalSize; _translations.Add(scriptBundleSet.Group, scriptBundleSet); var statusString = InstallerUtil.GenerateStatusString(scriptBundleSet.Scripts); var timespan = DateTime.UtcNow.Subtract(DateTime.SpecifyKind(DateTimeOffset.FromUnixTimeSeconds((long)lastModified).DateTime, DateTimeKind.Utc)); var lastUpdated = InstallerUtil.PeriodOfTimeOutput(timespan); bool enableButton = statusString.Item1 != AppResources.StatusInstalled; var i1 = scriptBundleSet.Group; if (!scriptBundleSet.Hidden) { _guiObjects.Add(new TranslationGUIObject() { BundleID = scriptBundleSet.Group, BundleHidden = scriptBundleSet.Hidden, InstallEnabled = enableButton, InstallClick = new Command(async() => await Install(region, i1), () => enableButton && ButtonsEnabled), Name = scriptBundleSet.Name, Status = statusString.Item1, TextColor = statusString.Item2, LastUpdated = lastUpdated, ButtonInstallText = statusString.Item1 != AppResources.StatusInstalled ? AppResources.InstallText : AppResources.InstalledText }); } } LoadTranslationList(); } else { LoadingText.Text = String.Format(AppResources.NoScriptsAvailable); SwitchErrorObjects(true); return; } }