public List <InstalledASIMod> GetInstalledASIs() { List <InstalledASIMod> installedASIs = new List <InstalledASIMod>(); try { string asiDirectory = MEDirectories.ASIPath(this); if (asiDirectory != null && Directory.Exists(TargetPath)) { if (!Directory.Exists(asiDirectory)) { Directory.CreateDirectory(asiDirectory); //Create it, but we don't need it return(installedASIs); //It won't have anything in it if we are creating it } var asiFiles = Directory.GetFiles(asiDirectory, @"*.asi"); foreach (var asiFile in asiFiles) { var hash = Utilities.CalculateMD5(asiFile); var matchingManifestASI = ASIManager.GetASIVersionByHash(hash, Game); if (matchingManifestASI != null) { installedASIs.Add(new KnownInstalledASIMod(asiFile, hash, Game, matchingManifestASI)); } else { installedASIs.Add(new UnknownInstalledASIMod(asiFile, hash, Game)); } } } } catch (Exception e) { Log.Error(@"Error fetching list of installed ASIs: " + e.Message); } return(installedASIs); }
/// <summary> /// Gets a list of installed ASI mods. /// </summary> /// <param name="game">Game to filter results by. Enter 1 2 or 3 for that game only, or anything else to get everything.</param> /// <returns></returns> private List <InstalledASIMod> getInstalledASIMods(Mod.MEGame game = Mod.MEGame.Unknown) { List <InstalledASIMod> results = new List <InstalledASIMod>(); if (SelectedTarget != null) { string asiDirectory = MEDirectories.ASIPath(SelectedTarget); string gameDirectory = ME1Directory.gamePath; if (asiDirectory != null && Directory.Exists(gameDirectory)) { if (!Directory.Exists(asiDirectory)) { Directory.CreateDirectory(asiDirectory); return(results); //It won't have anything in it if we are creating it } var asiFiles = Directory.GetFiles(asiDirectory, @"*.asi"); foreach (var asiFile in asiFiles) { results.Add(new InstalledASIMod(asiFile, game)); } } } return(results); }
/// <summary> /// Installs the specific version of an ASI to the specified target /// </summary> /// <param name="modVersion"></param> /// <param name="target"></param> /// <param name="forceSource">Null to let application choose the source, true to force online, false to force local cache. This parameter is used for testing</param> /// <returns></returns> public static bool InstallASIToTarget(ASIModVersion asi, GameTarget target, bool?forceSource = null) { if (asi.Game != target.Game) { throw new Exception($@"ASI {asi.Name} cannot be installed to game {target.Game}"); } Log.Information($@"Processing ASI installation request: {asi.Name} v{asi.Version} -> {target.TargetPath}"); string destinationFilename = $@"{asi.InstalledPrefix}-v{asi.Version}.asi"; string cachedPath = Path.Combine(CachedASIsFolder, destinationFilename); string destinationDirectory = MEDirectories.ASIPath(target); if (!Directory.Exists(destinationDirectory)) { Log.Information(@"Creating ASI directory in game: " + destinationDirectory); Directory.CreateDirectory(destinationDirectory); } string finalPath = Path.Combine(destinationDirectory, destinationFilename); // Delete existing ASIs from the same group to ensure we don't install the same mod var existingSameGroupMods = target.GetInstalledASIs().OfType <KnownInstalledASIMod>().Where(x => x.AssociatedManifestItem.OwningMod == asi.OwningMod).ToList(); bool hasExistingVersionOfModInstalled = false; if (existingSameGroupMods.Any()) { foreach (var v in existingSameGroupMods) { if (v.Hash == asi.Hash && !forceSource.HasValue && !hasExistingVersionOfModInstalled) //If we are forcing a source, we should always install. Delete duplicates past the first one { Log.Information($@"{v.AssociatedManifestItem.Name} is already installed. We will not remove the existing correct installed ASI for this install request"); hasExistingVersionOfModInstalled = true; continue; //Don't delete this one. We are already installed. There is no reason to install it again. } Log.Information($@"Deleting existing ASI from same group: {v.InstalledPath}"); v.Uninstall(); } } if (hasExistingVersionOfModInstalled && !forceSource.HasValue) //Let app decide { return(true); // This asi was "Installed" (because it was already installed). } // Install the ASI if (forceSource == null || forceSource.Value == false) { Debug.WriteLine("Hit me"); } string md5; bool useLocal = forceSource.HasValue && !forceSource.Value; // false (forceLocal) if (!useLocal && !forceSource.HasValue) { useLocal = File.Exists(cachedPath); } if (useLocal) { //Check hash first md5 = Utilities.CalculateMD5(cachedPath); if (md5 == asi.Hash) { Log.Information($@"Copying ASI from cached library to destination: {cachedPath} -> {finalPath}"); File.Copy(cachedPath, finalPath, true); Log.Information($@"Installed ASI to {finalPath}"); Analytics.TrackEvent(@"Installed ASI", new Dictionary <string, string>() { { @"Filename", Path.GetFileNameWithoutExtension(finalPath) } }); return(true); } } if (!forceSource.HasValue || forceSource.Value) { WebRequest request = WebRequest.Create(asi.DownloadLink); Log.Information(@"Fetching remote ASI from server"); using WebResponse response = request.GetResponse(); var memoryStream = new MemoryStream(); response.GetResponseStream().CopyTo(memoryStream); //MD5 check on file for security md5 = Utilities.CalculateMD5(memoryStream); if (md5 != asi.Hash) { //ERROR! Log.Error(@"Downloaded ASI did not match the manifest! It has the wrong hash."); return(false); } Log.Information(@"Fetched remote ASI from server. Installing ASI to " + finalPath); memoryStream.WriteToFile(finalPath); Log.Information(@"ASI successfully installed."); Analytics.TrackEvent(@"Installed ASI", new Dictionary <string, string>() { { @"Filename", Path.GetFileNameWithoutExtension(finalPath) } }); //Cache ASI if (!Directory.Exists(CachedASIsFolder)) { Log.Information(@"Creating cached ASIs folder"); Directory.CreateDirectory(CachedASIsFolder); } Log.Information(@"Caching ASI to local ASI library: " + cachedPath); memoryStream.WriteToFile(cachedPath); return(true); } // We could not install the ASI return(false); }
private void InstallASI(ASIMod asiToInstall, InstalledASIMod oldASIToRemoveOnSuccess = null, Action operationCompletedCallback = null) { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += (a, b) => { ASIModUpdateGroup g = getUpdateGroupByMod(asiToInstall); string destinationFilename = $@"{asiToInstall.InstalledPrefix}-v{asiToInstall.Version}.asi"; string cachedPath = Path.Combine(CachedASIsFolder, destinationFilename); string destinationDirectory = MEDirectories.ASIPath(SelectedTarget); string finalPath = Path.Combine(destinationDirectory, destinationFilename); string md5; if (File.Exists(cachedPath)) { //Check hash first md5 = BitConverter.ToString(System.Security.Cryptography.MD5.Create().ComputeHash(File.ReadAllBytes(cachedPath))).Replace(@"-", "").ToLower(); if (md5 == asiToInstall.Hash) { Log.Information($@"Copying local ASI from libary to destination: {cachedPath} -> {finalPath}"); File.Copy(cachedPath, finalPath); operationCompletedCallback?.Invoke(); return; } } WebRequest request = WebRequest.Create(asiToInstall.DownloadLink); Log.Information(@"Fetching remote ASI from server"); using WebResponse response = request.GetResponse(); MemoryStream memoryStream = new MemoryStream(); response.GetResponseStream().CopyTo(memoryStream); //MD5 check on file for security md5 = BitConverter.ToString(System.Security.Cryptography.MD5.Create().ComputeHash(memoryStream.ToArray())).Replace(@"-", "").ToLower(); if (md5 != asiToInstall.Hash) { //ERROR! Log.Error(@"Downloaded ASI did not match the manifest! It has the wrong hash."); } else { Log.Information(@"Fetched remote ASI from server. Installing ASI to " + finalPath); File.WriteAllBytes(finalPath, memoryStream.ToArray()); if (!Directory.Exists(CachedASIsFolder)) { Directory.CreateDirectory(CachedASIsFolder); } Log.Information(@"Caching ASI to local ASI library: " + cachedPath); File.WriteAllBytes(cachedPath, memoryStream.ToArray()); //cache it if (oldASIToRemoveOnSuccess != null) { File.Delete(oldASIToRemoveOnSuccess.InstalledPath); } }; }; worker.RunWorkerCompleted += (a, b) => { RefreshASIStates(); operationCompletedCallback?.Invoke(); }; worker.RunWorkerAsync(); }
public void TestASIManager() { GlobalTest.Init(); Random random = new Random(); Console.WriteLine(@"Loading ASI Manager Manifest"); ASIManager.LoadManifest(); var games = new[] { Mod.MEGame.ME1, Mod.MEGame.ME2, Mod.MEGame.ME3 }; foreach (var game in games) { var root = GlobalTest.GetTestGameFoldersDirectory(game); var normal = Path.Combine(root, "normal"); GameTarget gt = new GameTarget(game, normal, true, false); var asiDir = MEDirectories.ASIPath(gt); if (Directory.Exists(asiDir)) { // Clean slate Utilities.DeleteFilesAndFoldersRecursively(asiDir); } var asisForGame = ASIManager.GetASIModsByGame(game); // 1: Test Installs of upgrades of versions foreach (var asi in asisForGame) { // Install every version of an ASI and then ensure only one ASI of that type exists in the directory. foreach (var v in asi.Versions) { var sourceBools = new bool?[] { true, false, null }; //online, local, let app decide foreach (var sourceBool in sourceBools) { // INSTALL FROM SOURCE Console.WriteLine($@"Install source variable: {sourceBool}"); Assert.IsTrue(ASIManager.InstallASIToTarget(v, gt, sourceBool), $"Installation of ASI failed: {v.Name}"); Assert.AreEqual(1, Directory.GetFiles(asiDir).Length, "The count of files in the ASI directory is not 1 after install of an ASI!"); // Check is installed var installedASIs = gt.GetInstalledASIs().OfType <KnownInstalledASIMod>().ToList(); Assert.AreEqual(1, installedASIs.Count, "The amount of installed ASIs as fetched by GameTarget GetInstalledASIs() is not equal to 1!"); // Check it maps to the correct one. var instASI = installedASIs.First(); Assert.AreEqual(v, instASI.AssociatedManifestItem, "The parsed installed ASI does not match the one we fed to ASIManager.InstallASIToTarget()!"); // Rename it to something random so the next version has to find it by the hash and not the filename. var newPath = Path.Combine(asiDir, Guid.NewGuid() + ".asi"); File.Move(instASI.InstalledPath, newPath, false); // Ensure it still can be found. installedASIs = gt.GetInstalledASIs().OfType <KnownInstalledASIMod>().ToList(); Assert.AreEqual(1, installedASIs.Count, "The amount of installed ASIs as fetched by GameTarget GetInstalledASIs() is not equal to 1 after renaming the file!"); // Make multiple clones, to ensure all old ones get deleted on upgrades. for (int i = 0; i < 5; i++) { var clonePath = Path.Combine(asiDir, instASI.AssociatedManifestItem.InstalledPrefix + i + ".asi"); File.Copy(newPath, clonePath, true); } installedASIs = gt.GetInstalledASIs().OfType <KnownInstalledASIMod>().ToList(); Assert.AreEqual(6, installedASIs.Count, "The amount of installed ASIs as fetched by GameTarget GetInstalledASIs() is not equal to 6 after cloning the file 5 times!"); } } var finalASIsPreRandomization = gt.GetInstalledASIs(); int randomCount = 0; foreach (var iam in finalASIsPreRandomization) { // Test randomly editing it. byte[] randomData = new byte[256]; random.NextBytes(randomData); File.WriteAllBytes(iam.InstalledPath, randomData); randomCount++; var unknownInstalledASIs = gt.GetInstalledASIs().OfType <UnknownInstalledASIMod>().ToList(); Assert.AreEqual(randomCount, unknownInstalledASIs.Count, "Writing random bytes to installed ASI made amount of installed ASIs not correct!"); } foreach (var v in finalASIsPreRandomization) { // Test uninstall and remove Assert.IsTrue(v.Uninstall(), $"ASI failed to uninstall: {v.InstalledPath}"); } Assert.AreEqual(0, Directory.GetFiles(asiDir).Length, "Leftover files remain after uninstalling all ASIs from target"); } } }