private static void CreateAssetPatch(Settings settings, IBSA original, Fo3File originalRecord, Fo3File resultRecord, RecordAction recordAction) { string patchName; string patchFile; // @todo: memory stream and name with md5 using (var patchStream = new MemoryStream()) { // @todo: lock byte[] originalRecordBytes; if (original != null) { lock (original) { originalRecordBytes = originalRecord.GetData(original.Reader); } } else { originalRecordBytes = originalRecord.GetData(); } var referenceBytes = resultRecord.GetData(); BsDiff.Create(originalRecordBytes, referenceBytes, patchStream); patchStream.Seek(0, SeekOrigin.Begin); patchName = new Guid(MD5.Create().ComputeHash(patchStream)).ToString(); recordAction.PatchName = patchName; patchFile = string.Format("{0}\\{1}.patch", settings.GetTempFolder("Patches"), patchName); try { using (var fileStream = new FileStream(patchFile, FileMode.CreateNew)) { patchStream.Seek(0, SeekOrigin.Begin); patchStream.CopyTo(fileStream); } } catch (IOException exc) { // ignored } } }
private static void CreateTTWBSA(Recipe recipe, Settings settings, DirectoryInfo assetsDir, string name) { Console.Write("Creating {0} ... ", name); var filepair = new FilePair() { ResultName = name, Type = ContentType.BSA, }; var looseFiles = PrepareLooseFiles(assetsDir); var patchFile = Path.Combine(assetsDir.FullName, "ttw.patch.txt"); SortedDictionary <string, Tuple <string, string, string> > patches = null; if (File.Exists(patchFile)) { patches = ReadPatchFile(patchFile); } foreach (var file in looseFiles) { if (patches != null && patches.ContainsKey(file.Key)) { var patch = patches[file.Key]; var recordAction = new RecordAction { Origin = patch.Item1, Path = patch.Item2.Substring(0, patch.Item2.LastIndexOf("\\", StringComparison.Ordinal)), Name = patch.Item2.Substring(patch.Item2.LastIndexOf("\\", StringComparison.Ordinal) + 1), NewPath = patch.Item3.Substring(0, patch.Item3.LastIndexOf("\\", StringComparison.Ordinal)), NewName = patch.Item3.Substring(patch.Item3.LastIndexOf("\\", StringComparison.Ordinal) + 1), Type = ActionType.Rename | ActionType.Patch }; var referenceFile = $"{settings.CurrentDataPath}\\{patch.Item1}"; var referenceDir = new DirectoryInfo(Path.ChangeExtension(referenceFile, null)); var reference = new SortedDictionary <string, Fo3File>(); Parallel.ForEach(referenceDir.EnumerateFiles("*.*", SearchOption.AllDirectories).AsParallel(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, asset => { var assetFile = new Fo3File(asset) { Path = asset.GetPathRelative(referenceDir.FullName).ToLower() }; lock (reference) { reference.Add(asset.GetFullNameRelative(referenceDir.FullName).ToLower(), assetFile); } }); try { var originalRecord = reference[patch.Item2]; var resultRecord = new Fo3File(new FileInfo(Path.Combine(assetsDir.FullName, patch.Item3))); patchQueue.Enqueue(() => CreateAssetPatch(settings, null, originalRecord, resultRecord, recordAction)); } catch (Exception e) { recordAction.Error = e.Message; } filepair.Actions.Add(recordAction); } else { filepair.Actions.Add(new RecordAction { Path = file.Value.GetPathRelative(assetsDir.FullName).ToLower(), Name = file.Value.Name.ToLower(), Type = ActionType.New }); } } recipe.Files.Add(filepair); FlushRecipe(settings, recipe); Console.WriteLine("Done"); }
private static void ProcessBSAs(Recipe recipe, Settings settings) { Console.WriteLine("Processing BSAs ..."); foreach (var filepair in settings.Files.Where(fp => fp.Type == ContentType.BSA)) { Console.Write("{0} => {1} .", filepair.OriginalName, filepair.ResultName); var before = GC.GetTotalMemory(false); var original = BSA.Open(string.Format("{0}\\{1}", settings.Fo3DataPath, filepair.OriginalName)); original.BuildIndex(); var after = GC.GetTotalMemory(false); var referenceFile = string.Format("{0}\\{1}", settings.CurrentDataPath, filepair.ResultName); var referenceDir = new DirectoryInfo(Path.ChangeExtension(referenceFile, null)); Console.Write("."); SortedDictionary <string, Fo3File> reference = new SortedDictionary <string, Fo3File>(); Parallel.ForEach(referenceDir.EnumerateFiles("*.*", SearchOption.AllDirectories).AsParallel(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, file => { var assetFile = new Fo3File(file) { Path = file.GetPathRelative(referenceDir.FullName).ToLower() }; lock (reference) { reference.Add(file.GetFullNameRelative(referenceDir.FullName).ToLower(), assetFile); } }); Console.Write(". "); Parallel.ForEach(reference, new ParallelOptions { MaxDegreeOfParallelism = 1 /* Environment.ProcessorCount */ }, record => { Debug.Write(record.Key); if (original.IndexFullPath.ContainsKey(record.Key)) { if (!ByteArrayCompare(record.Value.Checksum, original.IndexFullPath[record.Key].Checksum)) { var originalRecord = original.IndexFullPath[record.Key]; var resultRecord = record.Value; var recordAction = new RecordAction { Path = record.Value.Path, Name = record.Value.Name, Type = ActionType.Copy | ActionType.Patch }; patchQueue.Enqueue(() => CreateAssetPatch(settings, original, originalRecord, resultRecord, recordAction)); filepair.Actions.Add(recordAction); Debug.WriteLine(" patch"); } else { filepair.Actions.Add(new RecordAction { Path = record.Value.Path, Name = record.Value.Name, Type = ActionType.Copy }); Debug.WriteLine(" copy"); } original.IndexFullPath.Remove(record.Key); } else if (original.IndexFileName.ContainsKey(BitConverter.ToString(record.Value.Checksum))) { var originalRecord = original.IndexFileName[BitConverter.ToString(record.Value.Checksum)]; if (original.IndexFullPath.ContainsKey(record.Key)) { original.IndexFullPath.Remove(record.Key); } filepair.Actions.Add(new RecordAction { Path = originalRecord.Path, Name = originalRecord.Name, Type = ActionType.Rename, NewPath = record.Value.Path, NewName = record.Value.Name }); Debug.WriteLine(" rename"); } else { filepair.Actions.Add(new RecordAction { Path = record.Value.Path, Name = record.Value.Name, Type = ActionType.New }); Debug.WriteLine(" new"); } Console.Write("\r{0} => {1} ... {2}/{3}", filepair.OriginalName, filepair.ResultName, filepair.Actions.Count, reference.Count); }); var i = 0; foreach (var newRecord in original.IndexFullPath) { filepair.Actions.Add(new RecordAction { Path = newRecord.Value.Path, Name = newRecord.Value.Name, Type = ActionType.Delete }); Console.Write("\r{0} => {1} ... D{2}/{3} ", filepair.OriginalName, filepair.ResultName, ++i, original.IndexFullPath.Count); Debug.WriteLine(newRecord.Key); } recipe.Files.Add(filepair); Console.Write("\r{0} => {1} ... ", filepair.OriginalName, filepair.ResultName); Console.WriteLine("Done "); #if DEBUG FlushRecipe(settings, recipe); #endif } Console.WriteLine("BSAs completed"); }