/// <summary> /// Injects mods as per selected in lsvMods. /// </summary> /// <param name="createBackup">Creates a backup of modified .pp files if true. Default is false.</param> /// <param name="checkConflicts">Checks for conflicts in pending mods. Default is true.</param> /// <param name="suppressPopups">If true, does not generate message boxes. Default is false.</param> public bool inject(bool createBackup = false, bool checkConflicts = true, bool suppressPopups = false) { initializeBench(); cancelPending = false; updateStatus("Compiling changes...", LogIcon.Processing); //Compile changes _7z.ProgressUpdatedEventArgs updateProgress; List<string> rsub = new List<string>(); List<Mod> mods = new List<Mod>(); List<Mod> unmods = new List<Mod>(); foreach (ListViewItem l in lsvMods.Items) { Mod m = (Mod)l.Tag; if (l.Checked != m.Installed) { if (l.Checked) { mods.Add(m); } else { unmods.Add(m); rsub.AddRange(m.SubFilenames); } } } if (mods.Count + unmods.Count == 0) { updateStatus("No changes in selected mods found.", LogIcon.Error, false); updateStatus("FAILED: No changes in selected mods", LogIcon.Error, false, true); return false; } //Reset controls setEnabled(false); btnCancel.Enabled = true; prgMinor.Value = 0; prgMajor.Value = 0; int index = 0; Action Fail = () => { setEnabled(true); prgMinor.Value = 0; prgMajor.Value = 0; }; //Check if directory exists if (!(Directory.Exists(Paths.AA2Play) && Directory.Exists(Paths.AA2Edit))) { updateStatus("AA2Play/AA2Edit is not installed/cannot be found", LogIcon.Error, false); updateStatus("FAILED: AA2Play/AA2Edit is not installed/cannot be found", LogIcon.Error, false, true); Fail(); return false; } refreshModList(true, txtSearch.Text); foreach (ListViewItem l in lsvMods.Items) { Mod m = (Mod)l.Tag; foreach (Mod n in mods) if (n.Filename == m.Filename) l.Checked = true; foreach (Mod n in unmods) if (n.Filename == m.Filename) l.Checked = false; } //Clear and create temp updateStatus("Clearing TEMP folder..."); updateStatus("Clearing temporary folders..."); if (Directory.Exists(Paths.TEMP)) TryDeleteDirectory(Paths.TEMP); if (Directory.Exists(Paths.WORKING)) TryDeleteDirectory(Paths.WORKING); if (!Directory.Exists(Paths.BACKUP)) Directory.CreateDirectory(Paths.BACKUP + @"\"); Directory.CreateDirectory(Paths.TEMP + @"\"); Directory.CreateDirectory(Paths.WORKING + @"\"); Directory.CreateDirectory(Paths.TEMP + @"\AA2_PLAY\"); Directory.CreateDirectory(Paths.TEMP + @"\AA2_MAKE\"); Directory.CreateDirectory(Paths.TEMP + @"\BACKUP\"); Directory.CreateDirectory(Paths.TEMP + @"\BACKUP\AA2_PLAY\"); Directory.CreateDirectory(Paths.TEMP + @"\BACKUP\AA2_MAKE\"); //Check conflicts if (checkConflicts) { updateStatus("Checking conflicts..."); Dictionary<string, List<Mod>> files = new Dictionary<string, List<Mod>>(); foreach (Mod m in modDict.Values) if (lsvMods.Items[m.Name].Checked) m.SubFilenames.ForEach(x => { if (files.ContainsKey(x)) files[x].Add(m); else files[x] = new List<Mod> { m }; }); //Set each subfile to their respective owner(s) bool conflict = false; foreach (ListViewItem item in lsvMods.Items) //Loop through list items { if (!item.Checked) //We only care about ones that are / will be installed continue; Mod m = item.Tag as Mod; //The current mod we are checking List<string> conflicts = files.Where(x => x.Value.Any(y => y.Name == m.Name)) //If the subfile is contained by the mod .Where(x => x.Value.Count > 1) //If there is more than one owner .Select(x => x.Key) .ToList(); //Convert it to a list if (conflicts.Count > 0) { conflict = true; foreach (string s in conflicts) updateStatus(item.Text + ": " + s, LogIcon.Error, false); item.BackColor = Color.FromArgb(255, 255, 160); } } if (conflict) { updateStatus("Collision detected.", LogIcon.Error, false); updateStatus("FAILED: The highlighted mods have conflicting files", LogIcon.Error, false, true); currentOwner.InvokeMessageBox("Some mods have been detected to have conflicting files.\nYou can use the log to manually fix the conflicting files in the mods (if they can be fixed) or you can proceed anyway by changing the relevant setting in the preferences.\nNote: if you proceed anyway, to uninstall you must uninstall mods in the reverse order you installed them to ensure that wrong files are not left behind.", "Collision detected", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); Fail(); return false; } } //Check for free space long total7zSize = mods.Select(x => new SevenZipExtractor(x.Filename).UnpackedSize) .Sum(x => (long)x); long totalAA2PlaySize = mods.Select(x => new SevenZipExtractor(x.Filename).Files .Where(y => y.Filename.StartsWith("AA2_PLAY")) .Select(y => y.UnpackedSize)) .Sum(x => x.Sum(y => (long)y)); long totalAA2EditSize = mods.Select(x => new SevenZipExtractor(x.Filename).Files .Where(y => y.Filename.StartsWith("AA2_MAKE")) .Select(y => y.UnpackedSize)) .Sum(x => x.Sum(y => (long)y)); AdditionDictionary requiredSizes = new AdditionDictionary(); requiredSizes[Paths.TEMP.Remove(1)] = total7zSize; if (Paths.TEMP.Remove(1) != Paths.AA2Edit.Remove(1)) requiredSizes[Paths.AA2Edit.Remove(1)] = totalAA2EditSize + 0x40000000; //an approx. extra 1gb for temp files if (Paths.TEMP.Remove(1) != Paths.AA2Play.Remove(1)) requiredSizes[Paths.AA2Play.Remove(1)] = totalAA2PlaySize + 0x40000000; //an approx. extra 1gb for temp files foreach (var kv in requiredSizes) { bool tryAgain = false; do { tryAgain = false; if (!IsEnoughFreeSpace(kv.Key, kv.Value)) { updateStatus("FAILED: There is not enough free space", LogIcon.Error, false); string spaces = requiredSizes.Select(x => "Drive " + x.Key + ":\\ : Required " + BytesToString(x.Value) + "; Available: " + BytesToString(new DriveInfo(kv.Key).AvailableFreeSpace)) .Aggregate((a, b) => a + "\n" + b); var result = currentOwner.InvokeMessageBox("There is not enough free space to allow an approximate safe installation.\n" + spaces, "Not enough free space", MessageBoxButtons.RetryCancel, MessageBoxIcon.Exclamation); switch(result) { case DialogResult.Retry: tryAgain = true; break; case DialogResult.Cancel: default: Fail(); return false; } } } while (tryAgain); } //Verify mods List<Mod> combined = mods.Concat(unmods).ToList(); List<string> estPP = new List<string>(); foreach (var m in combined) { SevenZipExtractor s; if (m.Installed) s = new SevenZipExtractor(m.BackupFilename); else s = new SevenZipExtractor(m.Filename); updateStatus("(" + index + "/" + combined.Count + ") Verifying " + m.Name + " (0%)...", LogIcon.Processing, false, true); foreach (var d in s.Files) { if (d.Attributes.HasFlag(Attributes.Directory) && d.Filename.Length > 9) { string n = d.Filename.Remove(0, 9); if (d.Filename.StartsWith("AA2_MAKE")) estPP.Add(Paths.AA2Edit + @"\" + n + ".pp"); else estPP.Add(Paths.AA2Play + @"\" + n + ".pp"); } } SevenZipBase.ProgressUpdatedEventArgs progress = (i) => { this.prgMinor.GetCurrentParent().HybridInvoke(() => { prgMinor.Value = i; }); updateStatus("(" + index + "/" + combined.Count + ") Verifying " + m.Name + " (" + i + "%)...", LogIcon.Processing, false, true); }; s.ProgressUpdated += progress; bool result = new Func<bool>(() => s.TestArchive()).SemiAsyncWait(); s.ProgressUpdated -= progress; if (!result) { updateStatus("FAILED: " + m.Name + " failed to verify.", LogIcon.Error, false); Fail(); return false; } } //Verify PPs foreach (string p in estPP) { updateStatus("(" + index + "/" + combined.Count + ") Verifying " + p.Remove(0, p.LastIndexOf('\\') + 1) + "...", LogIcon.Processing); if (File.Exists(p)) if (!ppHeader.VerifyHeader(p)) { updateStatus("FAILED: " + p.Remove(0, p.LastIndexOf('\\') + 1) + " failed to verify.", LogIcon.Error, false); Fail(); return false; } } //Extract all mods index = 0; string name = ""; updateProgress = (i) => { prgMinor.GetCurrentParent().HybridInvoke(() => { prgMinor.Value = i; }); updateStatus("(" + index + "/" + combined.Count + ") Extracting " + name + " (" + i + "%)...", LogIcon.Processing, false, true); }; _7z.ProgressUpdated += updateProgress; foreach (Mod item in combined) { index++; name = item.Name; updateStatus("(" + index + "/" + combined.Count + ") Extracting " + name + " (0%)...", LogIcon.Processing); if (mods.Contains(item)) _7z.Extract(item.Filename); else _7z.Extract(item.BackupFilename, Paths.TEMP + @"\BACKUP\"); prgMajor.Value = (100 * index / combined.Count); if (tryCancel()) { Fail(); return false; } } _7z.ProgressUpdated -= updateProgress; //Reached point of no return. btnCancel.Enabled = false; //Build ppQueue index = 0; Queue<basePP> ppQueue = new Queue<basePP>(); List<basePP> ppList = new List<basePP>(); updateStatus("Creating .pp file queue..."); List<string> tempPLAY = new List<string>(Directory.GetDirectories(Paths.TEMP + @"\BACKUP\AA2_PLAY", "jg2*", SearchOption.TopDirectoryOnly)); List<string> tempEDIT = new List<string>(Directory.GetDirectories(Paths.TEMP + @"\BACKUP\AA2_MAKE", "jg2*", SearchOption.TopDirectoryOnly)); foreach (string path in tempPLAY) { ppQueue.Enqueue(new basePP(path, Paths.AA2Play)); } foreach (string path in tempEDIT) { ppQueue.Enqueue(new basePP(path, Paths.AA2Edit)); } //Prioritise removing subfiles from mods that are being uninstalled while (ppQueue.Count > 0) { basePP bp = ppQueue.Dequeue(); var r = rsub.ToArray(); foreach (string s in r) { foreach (IWriteFile iw in bp.pp.Subfiles) if (bp.ppDir + "\\" + iw.Name == s.Remove(0, 9)) { rsub.Remove(s); bp.pp.Subfiles.Remove(iw); break; } } prgMinor.Style = ProgressBarStyle.Continuous; int i = 1; foreach (string s in Directory.GetFiles(bp.ppRAW)) { string fname = s.Remove(0, s.LastIndexOf('\\') + 1); foreach (IWriteFile sub in bp.pp.Subfiles) { if (fname == sub.Name) { bp.pp.Subfiles.Remove(sub); break; } } prgMinor.Value = (i * 100 / Directory.GetFiles(bp.ppRAW).Length); bp.pp.Subfiles.Add(new Subfile(s)); i++; } ppList.Add(bp); } tempPLAY = new List<string>(Directory.GetDirectories(Paths.TEMP + @"\AA2_PLAY", "jg2*", SearchOption.TopDirectoryOnly)); tempEDIT = new List<string>(Directory.GetDirectories(Paths.TEMP + @"\AA2_MAKE", "jg2*", SearchOption.TopDirectoryOnly)); //Sort the uninstalled .pp files back into the main queue foreach (string path in tempPLAY) { var p = new basePP(path, Paths.AA2Play); var o = ppList.Find(x => x.ppDir == p.ppDir); if (o != null) { p.pp = o.pp; ppList.Remove(o); } ppQueue.Enqueue(p); } foreach (string path in tempEDIT) { var p = new basePP(path, Paths.AA2Edit); var o = ppList.Find(x => x.ppDir == p.ppDir); if (o != null) { p.pp = o.pp; ppList.Remove(o); } ppQueue.Enqueue(p); } int ii = 0; prgMajor.Value = 0; prgMinor.Value = 0; foreach (basePP b in ppList) { ii++; updateStatus("(" + ii + "/" + ppList.Count + ") Reverting " + b.ppFile + " (0%)...", LogIcon.Processing); if (b.pp.Subfiles.Count > 0) { BackgroundWorker bb = b.pp.WriteArchive(b.pp.FilePath, createBackup); bb.ProgressChanged += ((s, e) => { prgMinor.GetCurrentParent().HybridInvoke(() => { prgMinor.Value = e.ProgressPercentage; }); updateStatus("(" + ii + "/" + ppList.Count + ") Reverting " + b.ppFile + " (" + e.ProgressPercentage + "%)...", LogIcon.Processing, false, true); }); bb.SemiAsyncWait(); } else { File.Delete(b.pp.FilePath); } prgMajor.Value = (100 * ii / ppList.Count); } prgMinor.Value = 0; prgMajor.Value = 0; //Process .pp files int initial = ppQueue.Count; updateTaskbarProgress(); index = 0; while (ppQueue.Count > 0) { basePP b = ppQueue.Dequeue(); updateStatus("(" + (index + 1) + "/" + initial + ") Injecting " + b.ppFile + " (0%)...", LogIcon.Processing); prgMinor.Style = ProgressBarStyle.Continuous; int i = 1; foreach (Mod m in mods) { foreach (string s in m.SubFilenames) { if (s.Contains(b.ppDir)) { string r = s.Remove(0, 9); string rs = r.Remove(0, r.LastIndexOf('\\') + 1); string workingdir = Paths.WORKING + "\\BACKUP\\" + m.Name.Replace(".7z", "").Replace(".zip", "") + "\\"; string directory; if (tempPLAY.Contains(b.ppRAW)) { directory = workingdir + "AA2_PLAY\\" + r.Remove(r.LastIndexOf('\\') + 1); } else { directory = workingdir + "AA2_MAKE\\" + r.Remove(r.LastIndexOf('\\') + 1); } Directory.CreateDirectory(directory); foreach (IWriteFile iw in b.pp.Subfiles) { if (iw.Name == rs) { using (FileStream fs = new FileStream(directory + rs, FileMode.Create)) { iw.WriteTo(fs); } } } } } } foreach (string s in rsub) { foreach (IWriteFile iw in b.pp.Subfiles) if (b.ppDir + "\\" + iw.Name == s.Remove(0, 9)) { b.pp.Subfiles.Remove(iw); break; } } foreach (string s in Directory.GetFiles(b.ppRAW)) { string fname = s.Remove(0, s.LastIndexOf('\\')+1); foreach (IWriteFile sub in b.pp.Subfiles) { if (fname == sub.Name) { b.pp.Subfiles.Remove(sub); break; } } prgMinor.Value = (100 * i / Directory.GetFiles(b.ppRAW).Length); b.pp.Subfiles.Add(new Subfile(s)); i++; } if (b.pp.Subfiles.Count > 0) { prgMinor.Value = 0; BackgroundWorker bb = b.pp.WriteArchive(b.pp.FilePath, createBackup); bb.ProgressChanged += ((s, e) => { prgMinor.GetCurrentParent().HybridInvoke(() => { prgMinor.Value = e.ProgressPercentage; }); updateStatus("(" + (index + 1) + "/" + initial + ") Injecting " + b.ppFile + " (" + e.ProgressPercentage + "%)...", LogIcon.Processing, false, true); }); bb.SemiAsyncWait(); } else { File.Delete(b.pp.FilePath); } //Loop complete TryDeleteDirectory(b.ppRAW + "\\"); index++; prgMajor.Value = (100 * index / initial); updateTaskbarProgress(); } int ind = 0; //Archive backups prgMinor.Value = 0; prgMajor.Value = 0; if (!Directory.Exists(Paths.WORKING + "\\BACKUP\\")) { Directory.CreateDirectory(Paths.WORKING + "\\BACKUP\\"); } List<string> tempBackup = new List<string>(Directory.GetDirectories(Paths.WORKING + "\\BACKUP\\")); foreach (string s in tempBackup) { ind++; prgMajor.Value = (100 * ind / tempBackup.Count); updateStatus("(" + ind + "/" + tempBackup.Count + ") Archiving backup of " + s + " (0%)...", LogIcon.Processing); updateProgress = (i) => { prgMinor.GetCurrentParent().HybridInvoke(() => { prgMinor.Value = i; }); updateStatus("(" + ind + "/" + tempBackup.Count + ") Archiving backup of " + s + " (" + i + "%)...", LogIcon.Processing, false, true); }; _7z.ProgressUpdated += updateProgress; string item = s.Remove(0, s.LastIndexOf('\\') + 1); string archive = Paths.BACKUP + "\\" + item + ".7z"; if (Directory.Exists(s + "\\AA2_PLAY\\")) { foreach (string sub in Directory.GetDirectories(s + "\\AA2_PLAY\\")) { string g = sub.Remove(0, s.Length + 1) + "\\"; _7z.Compress(archive, s, g); } } if (Directory.Exists(s + "\\AA2_MAKE\\")) { foreach (string sub in Directory.GetDirectories(s + "\\AA2_MAKE\\")) { string g = sub.Remove(0, s.Length + 1) + "\\"; _7z.Compress(archive, s, g); } } _7z.ProgressUpdated -= updateProgress; } //Finish up prgMinor.Style = ProgressBarStyle.Continuous; updateStatus("Finishing up..."); mods.AddRange(unmods); foreach (Mod m in unmods) { string s = Paths.BACKUP + "\\" + m.Name.Replace(".zip", ".7z"); if (File.Exists(s)) { tryDelete(s); } } Configuration.saveMods(modDict); TryDeleteDirectory(Paths.TEMP); TryDeleteDirectory(Paths.WORKING); updateStatus("Success!", LogIcon.OK, false); updateTaskbarProgress(TaskbarProgress.TaskbarStates.NoProgress); if (!suppressPopups) currentOwner.InvokeMessageBox("Mods successfully synced."); refreshModList(); return true; }