private void CompactAFCBackgroundThread(object sender, DoWorkEventArgs e) { var arguments = (Tuple <string, string>)e.Argument; string path = arguments.Item1; string NewAFCBaseName = arguments.Item2; string[] pccFiles = System.IO.Directory.GetFiles(path, "*.pcc"); string[] afcFiles = System.IO.Directory.GetFiles(path, "*.afc").Select(x => System.IO.Path.GetFileNameWithoutExtension(x).ToLower()).ToArray(); var ReferencedAFCAudio = new List <Tuple <string, int, int> >(); int i = 1; foreach (string pccPath in pccFiles) { BusyText = "Finding all referenced audio (" + i + "/" + pccFiles.Count() + ")"; using (IMEPackage pack = MEPackageHandler.OpenMEPackage(pccPath)) { List <IExportEntry> wwiseStreamExports = pack.Exports.Where(x => x.ClassName == "WwiseStream").ToList(); foreach (IExportEntry exp in wwiseStreamExports) { var afcNameProp = exp.GetProperty <NameProperty>("Filename"); if (afcNameProp != null && afcFiles.Contains(afcNameProp.ToString().ToLower())) { string afcName = afcNameProp.ToString().ToLower(); int readPos = exp.Data.Length - 8; int audioSize = BitConverter.ToInt32(exp.Data, exp.Data.Length - 8); int audioOffset = BitConverter.ToInt32(exp.Data, exp.Data.Length - 4); ReferencedAFCAudio.Add(new Tuple <string, int, int>(afcName, audioSize, audioOffset)); } } } i++; } ReferencedAFCAudio = ReferencedAFCAudio.Distinct().ToList(); //extract referenced audio BusyText = "Extracting referenced audio"; var extractedAudioMap = new Dictionary <Tuple <string, int, int>, byte[]>(); i = 1; foreach (Tuple <string, int, int> reference in ReferencedAFCAudio) { BusyText = "Extracting referenced audio (" + i + " / " + ReferencedAFCAudio.Count() + ")"; string afcPath = System.IO.Path.Combine(path, reference.Item1 + ".afc"); FileStream stream = new FileStream(afcPath, FileMode.Open, FileAccess.Read); stream.Seek(reference.Item3, SeekOrigin.Begin); byte[] extractedAudio = new byte[reference.Item2]; stream.Read(extractedAudio, 0, reference.Item2); stream.Close(); extractedAudioMap[reference] = extractedAudio; i++; } var newAFCEntryPointMap = new Dictionary <Tuple <string, int, int>, long>(); i = 1; string newAfcPath = System.IO.Path.Combine(path, NewAFCBaseName + ".afc"); if (File.Exists(newAfcPath)) { File.Delete(newAfcPath); } FileStream newAFCStream = new FileStream(newAfcPath, FileMode.CreateNew, FileAccess.Write); foreach (Tuple <string, int, int> reference in ReferencedAFCAudio) { BusyText = "Building new AFC file (" + i + " / " + ReferencedAFCAudio.Count() + ")"; newAFCEntryPointMap[reference] = newAFCStream.Position; //save entry point in map newAFCStream.Write(extractedAudioMap[reference], 0, extractedAudioMap[reference].Length); i++; } newAFCStream.Close(); extractedAudioMap = null; //clean out ram on next GC i = 1; foreach (string pccPath in pccFiles) { BusyText = "Updating audio references (" + i + "/" + pccFiles.Count() + ")"; using (IMEPackage pack = MEPackageHandler.OpenMEPackage(pccPath)) { bool shouldSave = false; List <IExportEntry> wwiseStreamExports = pack.Exports.Where(x => x.ClassName == "WwiseStream").ToList(); foreach (IExportEntry exp in wwiseStreamExports) { var afcNameProp = exp.GetProperty <NameProperty>("Filename"); if (afcNameProp != null && afcFiles.Contains(afcNameProp.ToString().ToLower())) { string afcName = afcNameProp.ToString().ToLower(); int readPos = exp.Data.Length - 8; int audioSize = BitConverter.ToInt32(exp.Data, exp.Data.Length - 8); int audioOffset = BitConverter.ToInt32(exp.Data, exp.Data.Length - 4); var key = new Tuple <string, int, int>(afcName, audioSize, audioOffset); long newOffset; if (newAFCEntryPointMap.TryGetValue(key, out newOffset)) { //its a match afcNameProp.Value = NewAFCBaseName; Application.Current.Dispatcher.Invoke(() => { exp.WriteProperty(afcNameProp); byte[] newData = exp.Data; Buffer.BlockCopy(BitConverter.GetBytes((int)newOffset), 0, newData, newData.Length - 4, 4); //update AFC audio offset exp.Data = newData; if (exp.DataChanged) { //don't mark for saving if the data didn't acutally change (e.g. trying to compact a compacted AFC). shouldSave = true; } }); } } } if (shouldSave) { Application.Current.Dispatcher.Invoke( () => { // Must run on the UI thread or the tool interop will throw an exception // because we are on a background thread. pack.save(); }); } } i++; } BusyText = "Rebuild complete"; System.Threading.Thread.Sleep(2000); }