public static bool AttemptMerge(IMEPackage vanillaPackage, IMEPackage modifiedVanillaPackage, IMEPackage targetPackage) { PackageDelta vanillaToModifiedDelta = PackageDelta.CalculateDelta(vanillaPackage, modifiedVanillaPackage); PackageDelta vanillaToTargetDelta = PackageDelta.CalculateDelta(vanillaPackage, targetPackage); string loggingPrefix = File.Exists(targetPackage.FilePath) ? Path.GetFileName(targetPackage.FilePath) : targetPackage.FilePath; //Check merge conditions var nameConflicts = vanillaToModifiedDelta.NameDeltas.Keys.Intersect(vanillaToTargetDelta.NameDeltas.Keys).ToList(); var importConflicts = vanillaToModifiedDelta.ImportDeltas.Keys.Intersect(vanillaToTargetDelta.ImportDeltas.Keys).ToList(); var exportConflicts = vanillaToModifiedDelta.ExportDeltas.Keys.Intersect(vanillaToTargetDelta.ExportDeltas.Keys).ToList(); Log.Information($@"[{loggingPrefix}] Performing three way merge pre-check"); //Name deltas if (nameConflicts.Count > 0) { //need to check if the conflicts result in same value, in this case it would not be a conflict. foreach (int nameIndex in nameConflicts) { var modifiedName = modifiedVanillaPackage.Names[nameIndex]; var targetName = targetPackage.Names[nameIndex]; if (modifiedName != targetName) { //Differing names in same spots. Log.Information($@"[{loggingPrefix}] Cannot merge files: Name index {nameIndex} is different between modified and target."); return(false); } } } if (importConflicts.Count > 0) { //todo } if (exportConflicts.Count > 0) { //hmm... this will be a tough one. foreach (int exportTableIndex in exportConflicts) { //we will have to check sizes if we ever hope to have way to merge this var modifiedData = modifiedVanillaPackage.Exports[exportTableIndex].Data; var targetData = targetPackage.Exports[exportTableIndex].Data; if (!modifiedData.SequenceEqual(targetData)) { Log.Information($@"[{loggingPrefix}] Cannot merge files: Export table index {exportTableIndex} data is different between modified and target."); return(false); } //We will have to ignore size here somehow... //var modifiedHeader = modifiedVanillaPackage.Exports[exportTableIndex].Header; //var targetHeader = targetPackage.Exports[exportTableIndex].Header; } } //Merge is OK to perform //Apply vanilla to modified delta to target package foreach (var nameDelta in vanillaToModifiedDelta.NameDeltas) { if (nameDelta.Key >= targetPackage.NameCount) { //add it Log.Information($@"[{loggingPrefix}] Adding name {nameDelta.Value}"); targetPackage.FindNameOrAdd(nameDelta.Value); } else { Log.Information($@"[{loggingPrefix}] Updating name index {nameDelta.Key} to {nameDelta.Value}"); targetPackage.replaceName(nameDelta.Key, nameDelta.Value); } } foreach (var exportDelta in vanillaToModifiedDelta.ExportDeltas) { if (exportDelta.Key >= targetPackage.ExportCount) { //add it Log.Information($@"[{loggingPrefix}] Adding export {exportDelta.Value.InstancedFullPath}"); targetPackage.AddExport(exportDelta.Value); //not sure if this is possible } else { //gonna need this reviewed, not entirely sure this is OK to do Log.Information($@"[{loggingPrefix}] Updating export {exportDelta.Value.InstancedFullPath}"); targetPackage.Exports[exportDelta.Key].Data = exportDelta.Value.Data; targetPackage.Exports[exportDelta.Key].Header = exportDelta.Value.Header; } } foreach (var importDelta in vanillaToModifiedDelta.ImportDeltas) { if (importDelta.Key >= targetPackage.ImportCount) { //add it Log.Information($@"[{loggingPrefix}] Adding import {importDelta.Value.InstancedFullPath}"); targetPackage.AddImport(importDelta.Value); //not sure if this is possible } else { Log.Information($@"[{loggingPrefix}] Updating import {importDelta.Value.InstancedFullPath}"); //gonna need this reviewed, not entirely sure this is OK to do //targetPackage.Imports[importDelta.Key].Data = importDelta.Value.Data; targetPackage.Imports[importDelta.Key].Header = importDelta.Value.Header; } } Log.Information($@"[{loggingPrefix}] Finished three way merge"); return(true); }
public void CreateLiveEditFile() { string filePath = LiveFaceFxEditorFilePath; File.Copy(Path.Combine(App.ExecFolder, "ME3EmptyLevel.pcc"), filePath); LiveFile = MEPackageHandler.OpenMEPackage(filePath); for (int i = 0; i < LiveFile.Names.Count; i++) { if (LiveFile.Names[i].Equals("ME3EmptyLevel")) { LiveFile.replaceName(i, Path.GetFileNameWithoutExtension(filePath)); } } var packguid = Guid.NewGuid(); var package = LiveFile.GetUExport(1); package.PackageGUID = packguid; LiveFile.PackageGuid = packguid; EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, SourceAnimSet.Export.Parent, LiveFile, null, true, out _); var gender = SourceAnimSet.Export.ObjectNameString.Last() switch { 'F' => FaceFXGender.Female, 'M' => FaceFXGender.Male, _ => FaceFXGender.NonSpkr }; ActorTag = null; try { if (gender is not FaceFXGender.NonSpkr) { bool isFemale = gender is FaceFXGender.Female; var bioConv = LiveFile.Exports.First(exp => exp.Class.ObjectName == "BioConversation"); var LiveFileAnimSet = LiveFile.FindExport(SourceAnimSet.Export.InstancedFullPath); var propName = isFemale ? "m_aMaleFaceSets" : "m_aFemaleFaceSets"; int idx = bioConv.GetProperty <ArrayProperty <ObjectProperty> >(propName).FindIndex(objProp => objProp.Value == LiveFileAnimSet.UIndex); if (idx is 0) { //player ActorTag = $"Player_{(isFemale ? 'F' : 'M')}"; IEntry ent; using (IMEPackage soldierFile = MEPackageHandler.OpenME3Package(Path.Combine(ME3Directory.CookedPCPath, "SFXCharacterClass_Soldier.pcc"))) { EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, soldierFile.GetUExport(isFemale ? 10327 : 10330), LiveFile, LiveFile.GetUExport(3), true, out ent); } ExportEntry actor = (ExportEntry)ent; LiveFile.AddToLevelActorsIfNotThere(actor); actor.WriteProperty(new NameProperty(ActorTag, "Tag")); using (IMEPackage clothingFile = MEPackageHandler.OpenME3Package(Path.Combine(ME3Directory.CookedPCPath, $"BIOG_HM{(isFemale ? "F" : "M")}_ARM_CTH_R.pcc"))) { var clothingPackage = EntryImporter.GetOrAddCrossImportOrPackage("CTHa", clothingFile, LiveFile); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, clothingFile.GetUExport(isFemale ? 1625 : 1966), LiveFile, clothingPackage, true, out ent); } ExportEntry bodyComponent = LiveFile.GetUExport(actor.GetProperty <ObjectProperty>("BodyMesh").Value); bodyComponent.WriteProperty(new ObjectProperty(ent.UIndex, "SkeletalMesh")); } else if (idx is 1) { //owner using IMEPackage parentFile = getParentFile(); foreach (ExportEntry export in parentFile.Exports.Where(exp => exp.ClassName is "SFXSeqAct_StartConversation" or "SFXSeqAct_StartAmbientConv")) { if (export.GetProperty <ObjectProperty>("Conv") is ObjectProperty convProp && parentFile.TryGetImport(convProp.Value, out var convImport) && convImport.ObjectName == bioConv.ObjectName) { ExportEntry seqVar = parentFile.GetUExport(export.GetProperty <ArrayProperty <StructProperty> >("VariableLinks")[0].GetProp <ArrayProperty <ObjectProperty> >("LinkedVariables")[0].Value); if (seqVar.ClassName == "BioSeqVar_ObjectFindByTag") { ActorTag = seqVar.GetProperty <NameProperty>("m_sObjectTagToFind").Value; if (!ActorTag.StartsWith("hench_", StringComparison.OrdinalIgnoreCase)) { importTaggedActor(parentFile); } } else { EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, parentFile.GetUExport(seqVar.GetProperty <ObjectProperty>("ObjValue").Value), LiveFile, LiveFile.GetUExport(3), true, out var ent); ExportEntry actor = (ExportEntry)ent; LiveFile.AddToLevelActorsIfNotThere(actor); ActorTag = actor.GetProperty <NameProperty>("Tag")?.Value.Name; if (ActorTag is null) { ActorTag = "ConvoOwner"; actor.WriteProperty(new NameProperty(ActorTag, "Tag")); } } break; } } } else { ActorTag = bioConv.GetProperty <ArrayProperty <NameProperty> >("m_aSpeakerList")[idx - 2].Value; if (!ActorTag.StartsWith("hench_", StringComparison.OrdinalIgnoreCase)) { using IMEPackage parentFile = getParentFile(); importTaggedActor(parentFile); } } } else { //find nonspkr linkage somehow } }