private static void TestR(ExportEntry export) { if (/*export.UIndex == 36224 && */ export.ClassName == "Material") { var obj = ObjectBinary.From <Material>(export); foreach (var v in obj.SM3MaterialResource.UniformPixelVectorExpressions) { if (v is MaterialUniformExpressionVectorParameter vp) { vp.DefaultA = ThreadSafeRandom.NextFloat(0, 1); vp.DefaultR = ThreadSafeRandom.NextFloat(); vp.DefaultG = ThreadSafeRandom.NextFloat(); vp.DefaultB = ThreadSafeRandom.NextFloat(); } else if (v is MaterialUniformExpressionAppendVector av) { } } foreach (var v in obj.SM3MaterialResource.UniformPixelVectorExpressions) { } export.WriteBinary(obj); } if (export.UIndex == 37354 || export.UIndex == 37355) { var vParms = VectorParameter.GetVectorParameters(export); if (vParms != null) { foreach (var vParm in vParms) { CFVector4 nv = new CFVector4(); nv.W = ThreadSafeRandom.NextFloat(2000); nv.X = ThreadSafeRandom.NextFloat(2000); nv.Y = ThreadSafeRandom.NextFloat(2000); nv.Z = ThreadSafeRandom.NextFloat(2000); vParm.ParameterValue = nv; } } } }
public static bool AddToLevelActorsIfNotThere(this IMEPackage pcc, params ExportEntry[] actors) { bool added = false; if (pcc.Exports.FirstOrDefault(exp => exp.ClassName == "Level") is ExportEntry levelExport) { Level level = ObjectBinary.From <Level>(levelExport); foreach (ExportEntry actor in actors) { if (!level.Actors.Contains(actor.UIndex)) { added = true; level.Actors.Add(actor.UIndex); } } levelExport.WriteBinary(level); } return(added); }
public static bool RandomizeExport(ExportEntry exp, RandomizationOption option) { if (!CanRandomize(exp, out var shiftDirection, out var min, out var max)) { return(false); } var shiftAmt = ThreadSafeRandom.NextFloat(min, max); var morphTarget = ObjectBinary.From <MorphTarget>(exp); foreach (var lm in morphTarget.MorphLODModels) { foreach (var v in lm.Vertices) { var posDelta = v.PositionDelta; if (shiftDirection == 0) // IN OUT //posDelta.X += shiftAmt; { posDelta.X *= shiftAmt; } if (shiftDirection == 1) // LEFT RIGHT //posDelta.Y += shiftAmt; { posDelta.Y *= shiftAmt; } if (shiftDirection == 2) // UP DOWN //posDelta.Z += shiftAmt; { posDelta.Z *= shiftAmt; } v.PositionDelta = posDelta; // Require reassignment } } exp.WriteBinary(morphTarget); return(true); }
public static SkeletalMesh FuzzSkeleton(ExportEntry export, RandomizationOption option) { // Goofs up the RefSkeleton values var objBin = ObjectBinary.From <SkeletalMesh>(export); foreach (var bone in objBin.RefSkeleton) { if (!bone.Name.Name.Contains("eye", StringComparison.InvariantCultureIgnoreCase) && !bone.Name.Name.Contains("sneer", StringComparison.InvariantCultureIgnoreCase) && !bone.Name.Name.Contains("nose", StringComparison.InvariantCultureIgnoreCase) && !bone.Name.Name.Contains("brow", StringComparison.InvariantCultureIgnoreCase) && !bone.Name.Name.Contains("jaw", StringComparison.InvariantCultureIgnoreCase)) { continue; } var v3 = bone.Position; v3.X *= ThreadSafeRandom.NextFloat(1 - (option.SliderValue / 2), 1 + option.SliderValue); v3.Y *= ThreadSafeRandom.NextFloat(1 - (option.SliderValue / 2), 1 + option.SliderValue); v3.Z *= ThreadSafeRandom.NextFloat(1 - (option.SliderValue / 2), 1 + option.SliderValue); bone.Position = v3; } export.WriteBinary(objBin); return(objBin); }
public static void BuildBioPGlobal(GameTarget target) { M3MergeDLC.RemoveMergeDLC(target); var loadedFiles = MELoadedFiles.GetFilesLoadedInGame(target.Game, gameRootOverride: target.TargetPath); //var mergeFiles = loadedFiles.Where(x => // x.Key.StartsWith(@"BioH_") && x.Key.Contains(@"_DLC_MOD_") && x.Key.EndsWith(@".pcc") && !x.Key.Contains(@"_LOC_") && !x.Key.Contains(@"_Explore.")); Log.Information($@"SQMMERGE: Building BioP_Global"); var appearanceInfo = new CaseInsensitiveDictionary <List <SquadmateInfoSingle> >(); int appearanceId = 255; // starting int currentConditional = STARTING_OUTFIT_CONDITIONAL; // Scan squadmate merge files var sqmSupercedances = M3Directories.GetFileSupercedances(target, new[] { @".sqm" }); if (sqmSupercedances.TryGetValue(SQUADMATE_MERGE_MANIFEST_FILE, out var infoList)) { infoList.Reverse(); foreach (var dlc in infoList) { Log.Information($@"SQMMERGE: Processing {dlc}"); var jsonFile = Path.Combine(M3Directories.GetDLCPath(target), dlc, target.Game.CookedDirName(), SQUADMATE_MERGE_MANIFEST_FILE); var infoPackage = JsonConvert.DeserializeObject <SquadmateMergeInfo>(File.ReadAllText(jsonFile)); if (!infoPackage.Validate(dlc, target, loadedFiles)) { continue; // skip this } // Enumerate all outfits listed for a single squadmate foreach (var outfit in infoPackage.Outfits) { List <SquadmateInfoSingle> list; // See if we already have an outfit list for this squadmate, maybe from another mod... if (!appearanceInfo.TryGetValue(outfit.HenchName, out list)) { list = new List <SquadmateInfoSingle>(); appearanceInfo[outfit.HenchName] = list; } outfit.ConditionalIndex = currentConditional++; // This is always incremented, so it might appear out of order in game files depending on how mod order is processed, that should be okay though. outfit.AppearanceId = appearanceId++; // may need adjusted outfit.DLCName = dlc; list.Add(outfit); Log.Information($@"SQMMERGE: ConditionalIndex for {outfit.HenchName} appearanceid {outfit.AppearanceId}: {outfit.ConditionalIndex}"); } } } if (appearanceInfo.Any()) { var biopGlobal = MEPackageHandler.OpenMEPackage(loadedFiles[@"BioP_Global.pcc"]); var lsk = biopGlobal.Exports.FirstOrDefault(x => x.ClassName == @"LevelStreamingKismet"); var persistentLevel = biopGlobal.FindExport(@"TheWorld.PersistentLevel"); // Clone LevelStreamingKismets foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { var fName = outfit.HenchPackage; var newLSK = EntryCloner.CloneEntry(lsk); newLSK.WriteProperty(new NameProperty(fName, @"PackageName")); if (target.Game.IsGame3()) { // Game 3 has _Explore files too fName += @"_Explore"; newLSK = EntryCloner.CloneEntry(lsk); newLSK.WriteProperty(new NameProperty(fName, @"PackageName")); } } } // Update BioWorldInfo // Doesn't have consistent number so we can't find it by instanced full path var bioWorldInfo = biopGlobal.Exports.FirstOrDefault(x => x.ClassName == @"BioWorldInfo"); var props = bioWorldInfo.GetProperties(); // Update Plot Streaming var plotStreaming = props.GetProp <ArrayProperty <StructProperty> >(@"PlotStreaming"); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { // find item to add to buildPlotElementObject(plotStreaming, outfit, target.Game, false); if (target.Game.IsGame3()) { buildPlotElementObject(plotStreaming, outfit, target.Game, true); } } } // Update StreamingLevels var streamingLevels = props.GetProp <ArrayProperty <ObjectProperty> >(@"StreamingLevels"); streamingLevels.ReplaceAll(biopGlobal.Exports.Where(x => x.ClassName == @"LevelStreamingKismet").Select(x => new ObjectProperty(x))); bioWorldInfo.WriteProperties(props); M3MergeDLC.GenerateMergeDLC(target, Guid.NewGuid()); // Save BioP_Global into DLC var cookedDir = Path.Combine(M3Directories.GetDLCPath(target), M3MergeDLC.MERGE_DLC_FOLDERNAME, target.Game.CookedDirName()); var outP = Path.Combine(cookedDir, @"BioP_Global.pcc"); biopGlobal.Save(outP); // Generate conditionals file if (target.Game.IsGame3()) { CNDFile cnd = new CNDFile(); cnd.ConditionalEntries = new List <CNDFile.ConditionalEntry>(); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { var scText = $@"(plot.ints[{GetSquadmateOutfitInt(outfit.HenchName, target.Game)}] == i{outfit.MemberAppearanceValue})"; var compiled = ME3ConditionalsCompiler.Compile(scText); cnd.ConditionalEntries.Add(new CNDFile.ConditionalEntry() { Data = compiled, ID = outfit.ConditionalIndex }); } } cnd.ToFile(Path.Combine(cookedDir, $@"Conditionals{M3MergeDLC.MERGE_DLC_FOLDERNAME}.cnd")); } else if (target.Game.IsGame2()) { var startupF = Path.Combine(cookedDir, $@"Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc"); var startup = MEPackageHandler.OpenMEPackageFromStream(Utilities.GetResourceStream( $@"MassEffectModManagerCore.modmanager.mergedlc.{target.Game}.Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc")); var conditionalClass = startup.FindExport($@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}.BioAutoConditionals"); // Add Conditional Functions FileLib fl = new FileLib(startup); bool initialized = fl.Initialize(new RelativePackageCache() { RootPath = M3Directories.GetBioGamePath(target) }); if (!initialized) { throw new Exception( $@"FileLib for script update could not initialize, cannot install conditionals"); } var funcToClone = startup.FindExport($@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}.BioAutoConditionals.TemplateFunction"); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { var func = EntryCloner.CloneEntry(funcToClone); func.ObjectName = $@"F{outfit.ConditionalIndex}"; func.indexValue = 0; var scText = new StreamReader(Utilities.GetResourceStream( $@"MassEffectModManagerCore.modmanager.squadmates.{target.Game}.HasOutfitOnConditional.txt")) .ReadToEnd(); scText = scText.Replace(@"%CONDITIONALNUM%", outfit.ConditionalIndex.ToString()); scText = scText.Replace(@"%SQUADMATEOUTFITPLOTINT%", outfit.AppearanceId.ToString()); scText = scText.Replace(@"%OUTFITINDEX%", outfit.MemberAppearanceValue.ToString()); (_, MessageLog log) = UnrealScriptCompiler.CompileFunction(func, scText, fl); if (log.AllErrors.Any()) { Log.Error($@"Error compiling function {func.InstancedFullPath}:"); foreach (var l in log.AllErrors) { Log.Error(l.Message); } throw new Exception(M3L.GetString(M3L.string_interp_errorCompilingConditionalFunction, func, string.Join('\n', log.AllErrors.Select(x => x.Message)))); } } } // Relink the conditionals chain UClass uc = ObjectBinary.From <UClass>(conditionalClass); uc.UpdateLocalFunctions(); uc.UpdateChildrenChain(); conditionalClass.WriteBinary(uc); startup.Save(startupF); } // Add startup package, member appearances if (target.Game.IsGame2()) { var bioEngine = Path.Combine(cookedDir, @"BIOEngine.ini"); var ini = DuplicatingIni.LoadIni(bioEngine); var startupSection = ini.GetOrAddSection(@"Engine.StartupPackages"); startupSection.Entries.Add(new DuplicatingIni.IniEntry(@"+DLCStartupPackage", $@"Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}")); startupSection.Entries.Add(new DuplicatingIni.IniEntry(@"+Package", $@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}")); ini.WriteToFile(bioEngine); } else if (target.Game.IsGame3()) { var mergeCoalFile = Path.Combine(M3Directories.GetDLCPath(target), M3MergeDLC.MERGE_DLC_FOLDERNAME, target.Game.CookedDirName(), $@"Default_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.bin"); var mergeCoal = CoalescedConverter.DecompileGame3ToMemory(new MemoryStream(File.ReadAllBytes(mergeCoalFile))); // Member appearances var bioUiDoc = XDocument.Parse(mergeCoal[@"BIOUI.xml"]); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { var entry = new Game3CoalescedValueEntry() { Section = @"sfxgame.sfxguidata_teamselect", Name = @"selectappearances", Type = 3, Value = StringStructParser.BuildCommaSeparatedSplitValueList(outfit.ToPropertyDictionary(), @"AvailableImage", @"HighlightImage", @"DeadImage", @"SilhouetteImage") }; Game3CoalescedHelper.AddArrayEntry(bioUiDoc, entry); } } mergeCoal[@"BIOUI.xml"] = bioUiDoc.ToString(); // Dynamic load mapping var bioEngineDoc = XDocument.Parse(mergeCoal[@"BIOEngine.xml"]); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { // // * <Section name="sfxgame.sfxengine"> // <Property name="dynamicloadmapping"> // <Value type="3">(ObjectName="BIOG_GesturesConfigDLC.RuntimeData",SeekFreePackageName="GesturesConfigDLC")</Value> var entry = new Game3CoalescedValueEntry() { Section = @"sfxgame.sfxengine", Name = @"dynamicloadmapping", Type = 3 }; entry.Values.Add($"(ObjectName=\"{outfit.AvailableImage}\",SeekFreePackageName=\"SFXHenchImages_{outfit.DLCName}\")"); // do not localize entry.Values.Add($"(ObjectName=\"{outfit.HighlightImage}\",SeekFreePackageName=\"SFXHenchImages_{outfit.DLCName}\")"); // do not localize Game3CoalescedHelper.AddArrayEntry(bioEngineDoc, entry); } } mergeCoal[@"BIOEngine.xml"] = bioEngineDoc.ToString(); CoalescedConverter.CompileFromMemory(mergeCoal).WriteToFile(mergeCoalFile); } } }
/// <summary> /// Copy ME2 Static art and collision into an ME3 file. /// By Kinkojiro /// </summary> /// <param name="Game">Target Game</param> /// <param name="BioPSource">Source BioP</param> /// <param name="tgtOutputfolder">OutputFolder</param> /// <param name="BioArtsToCopy">List of level source file locations</param> /// <param name="ActorsToMove">Dicitionary: key Actors, value filename, entry uid</param> /// <param name="AssetsToMove">Dictionary key: AssetInstancedPath, value filename, isimport, entry uid</param> /// <param name="fromreload">is reloaded json</param> public static async Task <List <string> > ConvertLevelToGame(MEGame Game, IMEPackage BioPSource, string tgtOutputfolder, string tgttfc, Action <string> callbackAction, LevelConversionData conversionData = null, bool fromreload = false, bool createtestlevel = false) { //VARIABLES / VALIDATION var actorclassesToMove = new List <string>() { "BlockingVolume", "SpotLight", "SpotLightToggleable", "PointLight", "PointLightToggleable", "SkyLight", "HeightFog", "LenseFlareSource", "StaticMeshActor", "BioTriggerStream", "BioBlockingVolume" }; var actorclassesToSubstitute = new Dictionary <string, string>() { { "BioBlockingVolume", "Engine.BlockingVolume" } }; var archetypesToSubstitute = new Dictionary <string, string>() { { "Default__BioBlockingVolume", "Default__BlockingVolume" } }; var fails = new List <string>(); string busytext = null; if ((BioPSource.Game == MEGame.ME2 && ME2Directory.DefaultGamePath == null) || (BioPSource.Game == MEGame.ME1 && ME1Directory.DefaultGamePath == null) || (BioPSource.Game == MEGame.ME3 && ME3Directory.DefaultGamePath == null) || BioPSource.Game == MEGame.UDK) { fails.Add("Source Game Directory not found"); return(fails); } //Get filelist from BioP, Save a copy in outputdirectory, Collate Actors and asset information if (!fromreload) { busytext = "Collating level files..."; callbackAction?.Invoke(busytext); conversionData = new LevelConversionData(Game, BioPSource.Game, null, null, null, new ConcurrentDictionary <string, string>(), new ConcurrentDictionary <string, (string, int)>(), new ConcurrentDictionary <string, (string, int, List <string>)>()); var supportedExtensions = new List <string> { ".pcc", ".u", ".upk", ".sfm" }; if (Path.GetFileName(BioPSource.FilePath).ToLowerInvariant().StartsWith("biop_") && BioPSource.Exports.FirstOrDefault(x => x.ClassName == "BioWorldInfo") is ExportEntry BioWorld) { string biopname = Path.GetFileNameWithoutExtension(BioPSource.FilePath); conversionData.GameLevelName = biopname.Substring(5, biopname.Length - 5); var lsks = BioWorld.GetProperty <ArrayProperty <ObjectProperty> >("StreamingLevels").ToList(); if (lsks.IsEmpty()) { fails.Add("No files found in level."); return(fails); } foreach (var l in lsks) { var lskexp = BioPSource.GetUExport(l.Value); var filename = lskexp.GetProperty <NameProperty>("PackageName"); if ((filename?.Value.ToString().ToLowerInvariant().StartsWith("bioa") ?? false) || (filename?.Value.ToString().ToLowerInvariant().StartsWith("biod") ?? false)) { var filePath = Directory.GetFiles(ME2Directory.DefaultGamePath, $"{filename.Value.Instanced}.pcc", SearchOption.AllDirectories).FirstOrDefault(); conversionData.FilesToCopy.TryAdd(filename.Value.Instanced, filePath); } } conversionData.BioPSource = $"{biopname}_{BioPSource.Game}"; BioPSource.Save(Path.Combine(tgtOutputfolder, $"{conversionData.BioPSource}.pcc")); BioPSource.Dispose(); } else { fails.Add("Requires an BioP to work with."); return(fails); } busytext = "Collating actors and assets..."; callbackAction?.Invoke(busytext); Parallel.ForEach(conversionData.FilesToCopy, (pccref) => { using IMEPackage pcc = MEPackageHandler.OpenMEPackage(pccref.Value); var sourcelevel = pcc.Exports.FirstOrDefault(l => l.ClassName == "Level"); if (ObjectBinary.From(sourcelevel) is Level levelbin) { foreach (var act in levelbin.Actors) { if (act < 1) { continue; } var actor = pcc.GetUExport(act); if (actorclassesToMove.Contains(actor.ClassName)) { conversionData.ActorsToMove.TryAdd($"{pccref.Key}.{actor.InstancedFullPath}", (pccref.Key, act)); HashSet <int> actorrefs = pcc.GetReferencedEntries(true, true, actor); foreach (var r in actorrefs) { var objref = pcc.GetEntry(r); if (objref != null) { if (objref.InstancedFullPath.Contains("PersistentLevel")) //remove components of actors { continue; } string instancedPath = objref.InstancedFullPath; if (objref.idxLink == 0) { instancedPath = $"{pccref.Key}.{instancedPath}"; } var added = conversionData.AssetsToMove.TryAdd(instancedPath, (pccref.Key, r, new List <string>() { pccref.Key })); if (!added) { conversionData.AssetsToMove[instancedPath].Item3.FindOrAdd(pccref.Key); if (r > 0 && conversionData.AssetsToMove[instancedPath].Item2 < 0) //Replace imports with exports if possible { var currentlist = conversionData.AssetsToMove[instancedPath].Item3; conversionData.AssetsToMove[instancedPath] = (pccref.Key, r, currentlist); } }
public static bool RunPlotManagerUpdate(GameTarget target) { Log.Information($@"Updating PlotManager for game: {target.TargetPath}"); var supercedances = M3Directories.GetFileSupercedances(target, new[] { @".pmu" }); Dictionary <string, string> funcMap = new(); List <string> combinedNames = new List <string>(); if (supercedances.TryGetValue(@"PlotManagerUpdate.pmu", out var supercedanes)) { supercedanes.Reverse(); // list goes from highest to lowest. We want to build in lowest to highest StringBuilder sb = null; string currentFuncNum = null; var metaMaps = M3Directories.GetMetaMappedInstalledDLC(target, false); foreach (var pmuDLCName in supercedanes) { var uiName = metaMaps[pmuDLCName]?.ModName ?? ThirdPartyServices.GetThirdPartyModInfo(pmuDLCName, target.Game)?.modname ?? pmuDLCName; combinedNames.Add(uiName); var text = File.ReadAllLines(Path.Combine(M3Directories.GetDLCPath(target), pmuDLCName, target.Game.CookedDirName(), @"PlotManagerUpdate.pmu")); foreach (var line in text) { if (line.StartsWith(@"public function bool F")) { if (sb != null) { funcMap[currentFuncNum] = sb.ToString(); Log.Information($@"PlotSync: Adding function {currentFuncNum} from {pmuDLCName}"); currentFuncNum = null; } sb = new StringBuilder(); sb.AppendLine(line); // Method name currentFuncNum = line.Substring(22); currentFuncNum = currentFuncNum.Substring(0, currentFuncNum.IndexOf('(')); if (int.TryParse(currentFuncNum, out var num)) { if (num <= 0) { Log.Error($@"Skipping plot manager update: Conditional {num} is not a valid number for use. Values must be greater than 0 and less than 2 billion."); Analytics.TrackEvent(@"Bad plot manager function", new Dictionary <string, string>() { { @"FunctionName", $@"F{currentFuncNum}" }, { @"DLCName", pmuDLCName } }); sb = null; return(false); } else if (num.ToString().Length != currentFuncNum.Length) { Log.Error($@"Skipping plot manager update: Conditional {currentFuncNum} is not a valid number for use. Values must not contain leading zeros"); Analytics.TrackEvent(@"Bad plot manager function", new Dictionary <string, string>() { { @"FunctionName", $@"F{currentFuncNum}" }, { @"DLCName", pmuDLCName } }); sb = null; return(false); } } else { Log.Error($@"Skipping plot manager update: Conditional {currentFuncNum} is not a valid number for use. Values must be greater than 0 and less than 2 billion."); Analytics.TrackEvent(@"Bad plot manager function", new Dictionary <string, string>() { { @"FunctionName", $@"F{currentFuncNum}" }, { @"DLCName", pmuDLCName } }); sb = null; return(false); } } else { sb?.AppendLine(line); } } // Add final, if any was found if (sb != null) { funcMap[currentFuncNum] = sb.ToString(); Log.Information($@"PlotSync: Adding function {currentFuncNum} from {pmuDLCName}"); } } } var pmPath = GetPlotManagerPath(target); var vpm = Utilities.ExtractInternalFileToStream($@"MassEffectModManagerCore.modmanager.plotmanager.{target.Game}.PlotManager.{(target.Game == MEGame.ME1 ? @"u" : @"pcc")}"); // do not localize if (funcMap.Any()) { var plotManager = MEPackageHandler.OpenMEPackageFromStream(vpm, $@"PlotManager.{(target.Game == MEGame.ME1 ? @"u" : @"pcc")}"); // do not localize var clonableFunction = plotManager.Exports.FirstOrDefault(x => x.ClassName == @"Function"); // STEP 1: ADD ALL NEW FUNCTIONS BEFORE WE INITIALIZE THE FILELIB. foreach (var v in funcMap) { var pmKey = $@"BioAutoConditionals.F{v.Key}"; var exp = plotManager.FindExport(pmKey); if (exp == null) { // Adding a new conditional exp = EntryCloner.CloneEntry(clonableFunction); exp.ObjectName = new NameReference($@"F{v.Key}", 0); exp.FileRef.InvalidateLookupTable(); // We changed the name. // Reduces trash UFunction uf = ObjectBinary.From <UFunction>(exp); uf.Children = 0; uf.ScriptBytes = Array.Empty <byte>(); // No script data exp.WriteBinary(uf); Log.Information($@"Generated new blank conditional function export: {exp.UIndex} {exp.InstancedFullPath}", Settings.LogModInstallation); } } // Relink child chain UClass uc = ObjectBinary.From <UClass>(plotManager.FindExport(@"BioAutoConditionals")); uc.UpdateChildrenChain(); uc.UpdateLocalFunctions(); uc.Export.WriteBinary(uc); // STEP 2: UPDATE FUNCTIONS Stopwatch sw = Stopwatch.StartNew(); var fl = new FileLib(plotManager); bool initialized = fl.Initialize(new RelativePackageCache() { RootPath = M3Directories.GetBioGamePath(target) }, target.TargetPath, canUseBinaryCache: false); if (!initialized) { Log.Error(@"Error initializing FileLib for plot manager sync:"); foreach (var v in fl.InitializationLog.AllErrors) { Log.Error(v.Message); } throw new Exception(M3L.GetString(M3L.string_interp_fileLibInitFailedPlotManager, string.Join(Environment.NewLine, fl.InitializationLog.AllErrors.Select(x => x.Message)))); //force localize } sw.Stop(); Debug.WriteLine($@"Took {sw.ElapsedMilliseconds}ms to load filelib"); bool relinkChain = false; foreach (var v in funcMap) { var pmKey = $@"BioAutoConditionals.F{v.Key}"; Log.Information($@"Updating conditional entry: {pmKey}", Settings.LogModInstallation); var exp = plotManager.FindExport(pmKey); (_, MessageLog log) = UnrealScriptCompiler.CompileFunction(exp, v.Value, fl); if (log.AllErrors.Any()) { Log.Error($@"Error compiling function {exp.InstancedFullPath}:"); foreach (var l in log.AllErrors) { Log.Error(l.Message); } throw new Exception(M3L.GetString(M3L.string_interp_errorCompilingFunctionReason, exp, string.Join('\n', log.AllErrors.Select(x => x.Message)))); } } if (plotManager.IsModified) { plotManager.Save(pmPath, true); // Update local file DB var bgfe = new BasegameFileIdentificationService.BasegameCloudDBFile(pmPath.Substring(target.TargetPath.Length + 1), (int)new FileInfo(pmPath).Length, target.Game, M3L.GetString(M3L.string_interp_plotManagerSyncForX, string.Join(@", ", combinedNames)), Utilities.CalculateMD5(pmPath)); BasegameFileIdentificationService.AddLocalBasegameIdentificationEntries(new List <BasegameFileIdentificationService.BasegameCloudDBFile>(new[] { bgfe })); } } else { // Just write out vanilla. vpm.WriteToFile(pmPath); } return(true); }
public static Class ConvertClass(UClass uClass, bool decompileBytecode, FileLib lib = null) { IMEPackage pcc = uClass.Export.FileRef; VariableType parent = new VariableType(pcc.GetEntry(uClass.SuperClass)?.ObjectName.Instanced ?? "object"); VariableType outer = new VariableType(pcc.GetEntry(uClass.OuterClass)?.ObjectName.Instanced ?? parent.Name); // TODO: components var interfaces = new List <VariableType>(); foreach ((UIndex interfaceUIndex, UIndex _) in uClass.Interfaces) { interfaces.Add(new VariableType(pcc.GetEntry(interfaceUIndex)?.ObjectName.Instanced ?? "UNK_INTERFACE")); } var Types = new List <VariableType>(); var Vars = new List <VariableDeclaration>(); var Funcs = new List <Function>(); var States = new List <State>(); var nextItem = uClass.Children; while (pcc.TryGetUExport(nextItem, out ExportEntry nextChild)) { var objBin = ObjectBinary.From(nextChild); switch (objBin) { case UConst uConst: Types.Add(new Const(uConst.Export.ObjectName.Instanced, uConst.Value) { Literal = new ClassOutlineParser(new TokenStream <string>(new StringLexer(uConst.Value))).ParseConstValue() }); nextItem = uConst.Next; break; case UEnum uEnum: Types.Add(ConvertEnum(uEnum)); nextItem = uEnum.Next; break; case UFunction uFunction: Funcs.Add(ConvertFunction(uFunction, uClass, decompileBytecode)); nextItem = uFunction.Next; break; case UProperty uProperty: Vars.Add(ConvertVariable(uProperty)); nextItem = uProperty.Next; break; case UScriptStruct uScriptStruct: Types.Add(ConvertStruct(uScriptStruct)); nextItem = uScriptStruct.Next; break; case UState uState: nextItem = uState.Next; States.Add(ConvertState(uState, uClass, decompileBytecode)); break; default: nextItem = 0; break; } } var propObject = pcc.GetUExport(uClass.Defaults); var defaultProperties = ConvertDefaultProperties(propObject); Class AST = new Class(uClass.Export.ObjectName.Instanced, parent, outer, uClass.ClassFlags, interfaces, Types, Vars, Funcs, States, defaultProperties) { ConfigName = uClass.ClassConfigName, Package = uClass.Export.Parent is null?Path.GetFileNameWithoutExtension(pcc.FilePath) : uClass.Export.ParentInstancedFullPath, }; // Ugly quick fix: foreach (var member in Types) { member.Outer = AST; } foreach (var member in Vars) { member.Outer = AST; } foreach (var member in Funcs) { member.Outer = AST; } foreach (var member in States) { member.Outer = AST; } var virtFuncLookup = new CaseInsensitiveDictionary <ushort>(); for (ushort i = 0; i < uClass.FullFunctionsList.Length; i++) { virtFuncLookup.Add(uClass.FullFunctionsList[i].GetEntry(pcc)?.ObjectName, i); } AST.VirtualFunctionLookup = virtFuncLookup; return(AST); }
internal static bool RandomizeExport(ExportEntry export, RandomizationOption option) { if (!CanRandomize(export)) { return(false); } #if DEBUG //if (!export.ObjectName.Name.Contains("HeavyWeaponMech")) // return false; #endif var powers = export.GetProperty <ArrayProperty <ObjectProperty> >("Powers"); if (powers == null) { // This loadout has no powers! // Randomly give them some powers. if (ThreadSafeRandom.Next(1) == 0) { // unlimited power List <ObjectProperty> blankPows = new List <ObjectProperty>(); // Add two blanks. We'll strip blanks before writing it blankPows.Add(new ObjectProperty(int.MinValue)); blankPows.Add(new ObjectProperty(int.MinValue)); powers = new ArrayProperty <ObjectProperty>(blankPows, "Powers"); } else { // Sorry mate no powers for you return(false); } } var originalPowerUIndexes = powers.Where(x => x.Value > 0).Select(x => x.Value).ToList(); foreach (var power in powers.ToList()) { if (power.Value == 0) { return(false); // Null entry in weapons list } IEntry existingPowerEntry = null; if (power.Value != int.MinValue) { // Husk AI kinda depends on melee or they just kinda breath on you all creepy like // We'll give them a chance to change it up though existingPowerEntry = power.ResolveToEntry(export.FileRef); if (existingPowerEntry.ObjectName.Name.Contains("Melee", StringComparison.InvariantCultureIgnoreCase) && ThreadSafeRandom.Next(2) == 0) { MERLog.Information($"Not changing melee power {existingPowerEntry.ObjectName.Name}"); continue; // Don't randomize power } if (PowersToNotSwap.Contains(existingPowerEntry.ObjectName.Name)) { MERLog.Information($"Not changing power {existingPowerEntry.ObjectName.Name}"); continue; // Do not change this power } } // DEBUG PowerInfo randomNewPower = Powers.RandomElement(); //if (option.SliderValue < 0) //{ // randomNewPower = Powers.RandomElement(); //} //else //{ // randomNewPower = Powers[(int)option.SliderValue]; //} // Prevent krogan from getting a death power while (export.ObjectName.Name.Contains("Krogan", StringComparison.InvariantCultureIgnoreCase) && randomNewPower.Type == EPowerCapabilityType.Death) { MERLog.Information(@"Re-roll no-death-power on krogan"); // Reroll. Krogan AI has something weird about it randomNewPower = Powers.RandomElement(); } // Prevent powerful enemies from getting super stat boosters while (randomNewPower.Type == EPowerCapabilityType.Buff && ( export.ObjectName.Name.Contains("Praetorian", StringComparison.InvariantCultureIgnoreCase) || export.ObjectName.Name.Contains("ShadowBroker", StringComparison.InvariantCultureIgnoreCase))) { MERLog.Information(@"Re-roll no-buffs for powerful enemy"); randomNewPower = Powers.RandomElement(); } #region YMIR MECH fixes if (export.ObjectName.Name.Contains("HeavyWeaponMech")) { // Heavy weapon mech chooses named death powers so we cannot change these // HeavyMechDeathExplosion is checked for existence. NormalExplosion for some reason isn't if ((existingPowerEntry.ObjectName.Name == "SFXPower_HeavyMechNormalExplosion")) { MERLog.Information($@"YMIR mech power HeavyMechNormalExplosion cannot be randomized, skipping"); continue; } // Do not add buff powers to YMIR while (randomNewPower.Type == EPowerCapabilityType.Buff) { MERLog.Information($@"Re-roll YMIR mech power to prevent potential enemy too difficult to kill softlock. Incompatible power: {randomNewPower.PowerName}"); randomNewPower = Powers.RandomElement(); } } #endregion // CHANGE THE POWER if (existingPowerEntry == null || randomNewPower.PowerName != existingPowerEntry.ObjectName) { if (powers.Any(x => power.Value != int.MinValue && power.ResolveToEntry(export.FileRef).ObjectName == randomNewPower.PowerName)) { continue; // Duplicate powers crash the game. It seems this code is not bulletproof here and needs changed a bit... } MERLog.Information($@"Changing power {export.ObjectName} {existingPowerEntry?.ObjectName ?? "(+New Power)"} => {randomNewPower.PowerName }"); // It's a different power. // See if we need to port this in var fullName = randomNewPower.PackageName + "." + randomNewPower.PowerName; // SFXGameContent_Powers.SFXPower_Hoops var existingVersionOfPower = export.FileRef.FindEntry(fullName); if (existingVersionOfPower != null) { // Power does not need ported in power.Value = existingVersionOfPower.UIndex; } else { // Power needs ported in power.Value = PortPowerIntoPackage(export.FileRef, randomNewPower, out _)?.UIndex ?? int.MinValue; } if (existingPowerEntry != null && existingPowerEntry.UIndex > 0 && PackageTools.IsPersistentPackage(export.FileRef.FilePath)) { // Make sure we add the original power to the list of referenced memory objects // so subfiles that depend on this power existing don't crash the game! var world = export.FileRef.FindExport("TheWorld"); var worldBin = ObjectBinary.From <World>(world); var extraRefs = worldBin.ExtraReferencedObjects.ToList(); extraRefs.Add(new UIndex(existingPowerEntry.UIndex)); worldBin.ExtraReferencedObjects = extraRefs.Distinct().ToArray(); // Filter out duplicates that may have already been in package world.WriteBinary(worldBin); } foreach (var addlPow in randomNewPower.AdditionalRequiredPowers) { var existingPow = export.FileRef.FindEntry(addlPow); //if (existingPow == null && randomNewPower.ImportOnly && sourcePackage != null) //{ // existingPow = PackageTools.CreateImportForClass(sourcePackage.FindExport(randomNewPower.PackageName + "." + randomNewPower.PowerName), export.FileRef); //} if (existingPow == null) { Debugger.Break(); } powers.Add(new ObjectProperty(existingPow)); } } } // Strip any blank powers we might have added, remove any duplicates powers.RemoveAll(x => x.Value == int.MinValue); powers.ReplaceAll(powers.ToList().Distinct()); //tolist prevents concurrent modification in nested linq // DEBUG #if DEBUG var duplicates = powers .GroupBy(i => i.Value) .Where(g => g.Count() > 1) .Select(g => g.Key).ToList(); if (duplicates.Any()) { foreach (var dup in duplicates) { Debug.WriteLine($"DUPLICATE POWER IN LOADOUT {export.FileRef.GetEntry(dup).ObjectName}"); } Debugger.Break(); } #endif export.WriteProperty(powers); // Our precalculated map should have accounted for imports already, so we odn't need to worry about missing imports upstream // If this is not a master or localization file (which are often used for imports) // Change the number around so it will work across packages. // May need disabled if game becomes unstable. // We check if less than 10 as it's very unlikely there will be more than 10 loadouts in a non-persistent package // if it's > 10 it's likely already a memory-changed item by MER var pName = Path.GetFileName(export.FileRef.FilePath); if (export.indexValue < 10 && !PackageTools.IsPersistentPackage(pName) && !PackageTools.IsLocalizationPackage(pName)) { export.ObjectName = new NameReference(export.ObjectName, ThreadSafeRandom.Next(2000)); } if (originalPowerUIndexes.Any()) { // We should ensure the original objects are still referenced so shared objects they have (vfx?) are kept in memory // Dunno if this actually fixes the problems... PackageTools.AddReferencesToWorld(export.FileRef, originalPowerUIndexes.Select(x => export.FileRef.GetUExport(x))); } return(true); }
/// <summary> /// Gets the defaults for this export - the export must be a class. Returns null if the defaults is an import. /// </summary> /// <param name="export"></param> /// <returns></returns> public static ExportEntry GetDefaults(this ExportEntry export) { return(export.FileRef.GetUExport(ObjectBinary.From <UClass>(export).Defaults)); }
public static HashSet <int> GetReferencedEntries(this IMEPackage pcc, bool getreferenced = true, bool getactorrefs = false, ExportEntry startatexport = null) { var result = new HashSet <int>(); Level level = null; Stack <IEntry> entriesToEvaluate = new Stack <IEntry>(); HashSet <IEntry> entriesEvaluated = new HashSet <IEntry>(); HashSet <IEntry> entriesReferenced = new HashSet <IEntry>(); if (startatexport != null) //Start at object { entriesToEvaluate.Push(startatexport); entriesReferenced.Add(startatexport); entriesEvaluated.Add(pcc.GetUExport(startatexport.idxLink)); //Do not go up the chain if parsing an export } else if (pcc.Exports.FirstOrDefault(exp => exp.ClassName == "Level") is ExportEntry levelExport) //Evaluate level with only actors, model+components, sequences and level class being processed. { level = ObjectBinary.From <Level>(levelExport); entriesEvaluated.Add(null); //null stops future evaluations entriesEvaluated.Add(levelExport); entriesReferenced.Add(levelExport); var levelclass = levelExport.Class; entriesToEvaluate.Push(levelclass); entriesReferenced.Add(levelclass); foreach (int actoridx in level.Actors) { var actor = pcc.GetEntry(actoridx); entriesToEvaluate.Push(actor); entriesReferenced.Add(actor); } var model = pcc.GetEntry(level.Model?.value ?? 0); entriesToEvaluate.Push(model); entriesReferenced.Add(model); foreach (var comp in level.ModelComponents) { var compxp = pcc.GetEntry(comp); entriesToEvaluate.Push(compxp); entriesReferenced.Add(compxp); } foreach (var seq in level.GameSequences) { var seqxp = pcc.GetEntry(seq); entriesToEvaluate.Push(seqxp); entriesReferenced.Add(seqxp); } var localpackage = pcc.Exports.FirstOrDefault(x => x.ClassName == "Package" && x.ObjectName.ToString().ToLower() == Path.GetFileNameWithoutExtension(pcc.FilePath).ToLower()); // Make sure world, localpackage, shadercache are all marked as referenced. entriesToEvaluate.Push(localpackage); entriesReferenced.Add(localpackage); var world = levelExport.Parent; entriesToEvaluate.Push(world); entriesReferenced.Add(world); var shadercache = pcc.Exports.FirstOrDefault(x => x.ClassName == "ShaderCache"); if (shadercache != null) { entriesEvaluated.Add(shadercache); entriesReferenced.Add(shadercache); entriesToEvaluate.Push(shadercache.Class); entriesReferenced.Add(shadercache.Class); } } else { return(result); //If this has no level it is a reference / seekfree package and shouldn't be compacted. } var theserefs = new HashSet <IEntry>(); while (!entriesToEvaluate.IsEmpty()) { var ent = entriesToEvaluate.Pop(); try { if (entriesEvaluated.Contains(ent) || (ent?.UIndex ?? 0) == 0 || (getactorrefs && !ent.InstancedFullPath.Contains("PersistentLevel"))) { continue; } entriesEvaluated.Add(ent); if (ent.idxLink != 0) { theserefs.Add(pcc.GetEntry(ent.idxLink)); } if (ent.UIndex < 0) { continue; } var exp = pcc.GetUExport(ent.UIndex); //find header references only if doing non-actors if (!getactorrefs) { if ((exp.Archetype?.UIndex ?? 0) != 0) { theserefs.Add(exp.Archetype); } if ((exp.Class?.UIndex ?? 0) != 0) { theserefs.Add(exp.Class); } if ((exp.SuperClass?.UIndex ?? 0) != 0) { theserefs.Add(exp.SuperClass); } if (exp.HasComponentMap) { foreach (var kvp in exp.ComponentMap) { //theserefs.Add(pcc.GetEntry(kvp.Value)); //THIS IS INCORRECT SHOULD NOT BE ON UINDEX } } } else { exp.CondenseArchetypes(); } //find property references findPropertyReferences(exp.GetProperties(), exp); //find binary references if (!exp.IsDefaultObject && ObjectBinary.From(exp) is ObjectBinary objBin) { List <(UIndex, string)> indices = objBin.GetUIndexes(exp.FileRef.Game); foreach ((UIndex uIndex, string propName) in indices) { if (uIndex != exp.UIndex) { theserefs.Add(pcc.GetEntry(uIndex)); } } } foreach (var reference in theserefs) { if (!entriesEvaluated.Contains(reference)) { entriesToEvaluate.Push(reference); entriesReferenced.Add(reference); } } theserefs.Clear(); } catch (Exception e) { Console.WriteLine($"Error getting references {ent.UIndex} {ent.ObjectName.Instanced}: {e.Message}"); } } if (getreferenced) { foreach (var entry in entriesReferenced) { result.Add(entry?.UIndex ?? 0); } } else { foreach (var xp in pcc.Exports) { if (!entriesReferenced.Contains(xp)) { result.Add(xp?.UIndex ?? 0); } } foreach (var im in pcc.Imports) { if (!entriesReferenced.Contains(im)) { result.Add(im?.UIndex ?? 0); } } } return(result); void findPropertyReferences(PropertyCollection props, ExportEntry exp) { foreach (Property prop in props) { switch (prop) { case ObjectProperty objectProperty: if (objectProperty.Value != 0 && objectProperty.Value != exp.UIndex) { theserefs.Add(pcc.GetEntry(objectProperty.Value)); } break; case DelegateProperty delegateProperty: if (delegateProperty.Value.Object != 0 && delegateProperty.Value.Object != exp.UIndex) { theserefs.Add(pcc.GetEntry(delegateProperty.Value.Object)); } break; case StructProperty structProperty: findPropertyReferences(structProperty.Properties, exp); break; case ArrayProperty <ObjectProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { ObjectProperty objProp = arrayProperty[i]; if (objProp.Value != 0 && objProp.Value != exp.UIndex) { theserefs.Add(pcc.GetEntry(objProp.Value)); } } break; case ArrayProperty <StructProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { StructProperty structProp = arrayProperty[i]; findPropertyReferences(structProp.Properties, exp); } break; } } } }
private static bool RandomizeWeaponLoadout(ExportEntry export, RandomizationOption option) { // Check for blacklisted changes // HACK FOR NOW until we have better solution in place if (export.ObjectName.Name.Contains("HammerHead", StringComparison.InvariantCultureIgnoreCase)) { return(false); // Do not randomize hammerhead } var guns = export.GetProperty <ArrayProperty <ObjectProperty> >("Weapons"); if (guns.Count == 1) //Randomizing multiple guns could be difficult and I'm not sure enemies ever change their weapons. { var gun = guns[0]; if (gun.Value == 0) { return(false); // Null entry in weapons list } var allowedGuns = GetAllowedWeaponsForLoadout(export); if (allowedGuns.Any()) { var randomNewGun = allowedGuns.RandomElementByWeight(x => x.Weight); if (option.HasSliderOption && option.SliderValue >= 0) { randomNewGun = AllAvailableWeapons[(int)option.SliderValue]; } //if (ThreadSafeRandom.Next(1) == 0) //{ // randomNewGun = allowedGuns.FirstOrDefault(x => x.GunName.Contains("GrenadeLauncher")); //} var originalGun = gun.ResolveToEntry(export.FileRef); if (randomNewGun.GunName != originalGun.ObjectName) { var gunInfo = randomNewGun; MERLog.Information($@"Changing gun {export.ObjectName} => {randomNewGun.GunName}"); // It's a different gun. // See if we need to port this in var fullName = gunInfo.PackageName + "." + randomNewGun.GunName; var repoint = export.FileRef.FindEntry(fullName); if (repoint != null) { // Gun does not need ported in gun.Value = repoint.UIndex; } else { // Gun needs ported in var newEntry = PortWeaponIntoPackage(export.FileRef, randomNewGun); gun.Value = newEntry.UIndex; } //if (!tried) export.WriteProperty(guns); var pName = Path.GetFileName(export.FileRef.FilePath); // If this is not a master or localization file (which are often used for imports) // Change the number around so it will work across packages. // May need disabled if game becomes unstable. // We check if less than 10 as it's very unlikely there will be more than 10 loadouts in a non-persistent package // if it's > 10 it's likely already a memory-changed item by MER var isPersistentPackage = PackageTools.IsPersistentPackage(pName); if (export.indexValue < 10 && !isPersistentPackage && !PackageTools.IsLocalizationPackage(pName)) { export.ObjectName = new NameReference(export.ObjectName, ThreadSafeRandom.Next(2000) + 10); } else if (isPersistentPackage && originalGun.UIndex > 0) { // Make sure we add the original gun to the list of referenced memory objects // so subfiles that depend on this gun existing don't crash the game! var world = export.FileRef.FindExport("TheWorld"); var worldBin = ObjectBinary.From <World>(world); var extraRefs = worldBin.ExtraReferencedObjects.ToList(); extraRefs.Add(new UIndex(originalGun.UIndex)); worldBin.ExtraReferencedObjects = extraRefs.Distinct().ToArray(); // Filter out duplicates that may have already been in package world.WriteBinary(worldBin); } tried = true; } } return(true); } return(false); }
public static Dictionary <IEntry, List <string> > FindUsagesOfName(this IMEPackage pcc, string name) { var result = new Dictionary <IEntry, List <string> >(); foreach (ExportEntry exp in pcc.Exports) { try { //find header references if (exp.ObjectName.Name == name) { result.AddToListAt(exp, "Header: Object Name"); } if (exp.HasComponentMap && exp.ComponentMap.Any(kvp => kvp.Key.Name == name)) { result.AddToListAt(exp, "Header: ComponentMap"); } if ((!exp.IsDefaultObject && exp.IsA("Component") || pcc.Game == MEGame.UDK && exp.ClassName.EndsWith("Component")) && exp.ParentFullPath.Contains("Default__") && exp.DataSize >= 12 && BitConverter.ToInt32(exp.Data, 4) is int nameIdx && pcc.IsName(nameIdx) && pcc.GetNameEntry(nameIdx) == name) { result.AddToListAt(exp, "Component TemplateName (0x4)"); } //find property references findPropertyReferences(exp.GetProperties(), exp, false, "Property: "); //find binary references if (!exp.IsDefaultObject && ObjectBinary.From(exp) is { } objBin) { if (objBin is BioStage bioStage) { if (bioStage.length > 0 && name == "m_aCameraList") { result.AddToListAt(exp, "(Binary prop: m_aCameraList name)"); } int i = 0; foreach ((NameReference key, PropertyCollection props) in bioStage.CameraList) { if (key.Name == name) { result.AddToListAt(exp, $"(Binary prop: m_aCameraList[{i}])"); } findPropertyReferences(props, exp, false, "Binary prop: m_aCameraList[{i}]."); ++i; } } else if (objBin is UScriptStruct scriptStruct) { findPropertyReferences(scriptStruct.Defaults, exp, false, "Binary Property:"); } else { List <(NameReference, string)> names = objBin.GetNames(exp.FileRef.Game); foreach ((NameReference nameRef, string propName) in names) { if (nameRef.Name == name) { result.AddToListAt(exp, $"(Binary prop: {propName})"); } } } } } catch { result.AddToListAt(exp, "Exception occured while reading this export!"); } } foreach (ImportEntry import in pcc.Imports) { try { if (import.ObjectName.Name == name) { result.AddToListAt(import, "ObjectName"); } if (import.PackageFile == name) { result.AddToListAt(import, "PackageFile"); } if (import.ClassName == name) { result.AddToListAt(import, "Class"); } } catch (Exception e) { result.AddToListAt(import, $"Exception occurred while reading this import: {e.Message}"); } } return(result); void findPropertyReferences(PropertyCollection props, ExportEntry exp, bool isInImmutable = false, string prefix = "") { foreach (Property prop in props) { if (!isInImmutable && prop.Name.Name == name) { result.AddToListAt(exp, $"{prefix}{prop.Name} name"); } switch (prop) { case NameProperty nameProperty: if (nameProperty.Value.Name == name) { result.AddToListAt(exp, $"{prefix}{nameProperty.Name} value"); } break; case DelegateProperty delegateProperty: if (delegateProperty.Value.FunctionName.Name == name) { result.AddToListAt(exp, $"{prefix}{delegateProperty.Name} function name"); } break; case EnumProperty enumProperty: if (pcc.Game >= MEGame.ME3 && !isInImmutable && enumProperty.EnumType.Name == name) { result.AddToListAt(exp, $"{prefix}{enumProperty.Name} enum type"); } if (enumProperty.Value.Name == name) { result.AddToListAt(exp, $"{prefix}{enumProperty.Name} enum value"); } break; case StructProperty structProperty: if (!isInImmutable && structProperty.StructType == name) { result.AddToListAt(exp, $"{prefix}{structProperty.Name} struct type"); } findPropertyReferences(structProperty.Properties, exp, structProperty.IsImmutable, $"{prefix}{structProperty.Name}: "); break; case ArrayProperty <NameProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { NameProperty nameProp = arrayProperty[i]; if (nameProp.Value.Name == name) { result.AddToListAt(exp, $"{prefix}{arrayProperty.Name}[{i}]"); } } break; case ArrayProperty <EnumProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { EnumProperty enumProp = arrayProperty[i]; if (enumProp.Value.Name == name) { result.AddToListAt(exp, $"{prefix}{arrayProperty.Name}[{i}]"); } } break; case ArrayProperty <StructProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { StructProperty structProp = arrayProperty[i]; findPropertyReferences(structProp.Properties, exp, structProp.IsImmutable, $"{prefix}{arrayProperty.Name}[{i}]."); } break; } } } }
/* * private static void VerifyGesturesWork(ExportEntry trackExport) * { * var gestures = RBioEvtSysTrackGesture.GetGestures(trackExport); * var defaultPose = RBioEvtSysTrackGesture.GetDefaultPose(trackExport); * * var gesturesToCheck = gestures.Append(defaultPose).ToList(); * * // Get the containing sequence * var owningSequence = SeqTools.GetParentSequence(trackExport); * while (owningSequence.ClassName != "Sequence") * { * owningSequence = owningSequence.Parent as ExportEntry; * var parSeq = SeqTools.GetParentSequence(owningSequence); * if (parSeq != null) * { * owningSequence = parSeq; * } * } * * var kismetBioDynamicAnimSets = owningSequence.GetProperty<ArrayProperty<ObjectProperty>>("m_aBioDynAnimSets"); * if (kismetBioDynamicAnimSets == null) * { * // We don't have any animsets! * throw new Exception("Track's sequence is missing animsets property!"); * } * * // Get a list of all supported animations * List<Gesture> supportedGestures = new List<Gesture>(); * foreach (var kbdas in kismetBioDynamicAnimSets) * { * var sequenceBioDynamicAnimSet = kbdas.ResolveToEntry(trackExport.FileRef) as ExportEntry; // I don't think these can be imports as they're part of the seq * var associatedset = sequenceBioDynamicAnimSet.GetProperty<ObjectProperty>("m_pBioAnimSetData").ResolveToEntry(trackExport.FileRef); * * } * * // Check all gestures * foreach (var gesture in gesturesToCheck) * { * var bioAnimSet = gesture.GetBioAnimSet(trackExport.FileRef); * * } * * * * } * * internal class TestingBioDynamicAnimSet * { * public NameReference OrigSetName { get; } * public List<string> SupportedGesturesFullPaths { get; } * public IEntry BioAnimSetData { get; } * * internal TestingBioDynamicAnimSet(ExportEntry export) * { * var props = export.GetProperties(); * OrigSetName = props.GetProp<NameProperty>("m_nmOrigSetName").Value; * BioAnimSetData = props.GetProp<ObjectProperty>("m_pBioAnimSetData").ResolveToEntry(export.FileRef); * SupportedGesturesFullPaths = props.GetProp<ArrayProperty<ObjectProperty>>("Sequences").Select(x => x.ResolveToEntry(export.FileRef).InstancedFullPath).ToList(); * } * } */ private static void InstallDynamicAnimSetRefForSeq(ref ExportEntry owningSequence, ExportEntry export, Gesture gesture) { // Find owning sequence if (owningSequence == null) { owningSequence = export; } while (owningSequence.ClassName != "Sequence") { owningSequence = owningSequence.Parent as ExportEntry; var parSeq = SeqTools.GetParentSequence(owningSequence); if (parSeq != null) { owningSequence = parSeq; } } // We have parent sequence data var kismetBioDynamicAnimSets = owningSequence.GetProperty <ArrayProperty <ObjectProperty> >("m_aBioDynAnimSets") ?? new ArrayProperty <ObjectProperty>("m_aBioDynamicAnimSets"); // Check to see if there is any item that uses our bioanimset var bioAnimSet = gesture.GetBioAnimSet(export.FileRef); if (bioAnimSet != null) { ExportEntry kismetBDAS = null; foreach (var kbdas in kismetBioDynamicAnimSets) { var kEntry = kbdas.ResolveToEntry(export.FileRef) as ExportEntry; // I don't think these can be imports as they're part of the seq var associatedset = kEntry.GetProperty <ObjectProperty>("m_pBioAnimSetData").ResolveToEntry(export.FileRef); if (associatedset == bioAnimSet) { // It's this one kismetBDAS = kEntry; break; } } if (kismetBDAS == null) { // We need to generate a new one PropertyCollection props = new PropertyCollection(); props.Add(new NameProperty(gesture.GestureSet, "m_nmOrigSetName")); props.Add(new ArrayProperty <ObjectProperty>("Sequences")); props.Add(new ObjectProperty(bioAnimSet, "m_pBioAnimSetData")); kismetBDAS = ExportCreator.CreateExport(export.FileRef, $"KIS_DYN_{gesture.GestureSet}", "BioDynamicAnimSet", owningSequence); kismetBDAS.indexValue = 0; // Write a blank count of 0 - we will update this in subsequent call // This must be here to ensure parser can read it kismetBDAS.WritePropertiesAndBinary(props, new byte[4]); kismetBioDynamicAnimSets.Add(new ObjectProperty(kismetBDAS)); // Add new export to sequence's list of biodynamicanimsets owningSequence.WriteProperty(kismetBioDynamicAnimSets); } var currentObjs = kismetBDAS.GetProperty <ArrayProperty <ObjectProperty> >("Sequences"); if (currentObjs.All(x => x.Value != gesture.Entry.UIndex)) { // We need to add our item to it currentObjs.Add(new ObjectProperty(gesture.Entry)); var bin = ObjectBinary.From <BioDynamicAnimSet>(kismetBDAS); bin.SequenceNamesToUnkMap[gesture.GestureAnim] = 1; // Not sure what the value should be, or if game actually reads this // FIX IT IF WE EVER FIGURE IT OUT! kismetBDAS.WriteProperty(currentObjs); kismetBDAS.WriteBinary(bin); } } }
private static bool ForcedRun(ExportEntry hairMeshExport) { if (hairMeshExport.GetProperty <ObjectProperty>("SkeletalMesh") is ObjectProperty obj && obj.Value != 0 && obj.ResolveToEntry(hairMeshExport.FileRef) is IEntry entry) { var isfemaleHair = entry.ObjectName.Name.StartsWith("HMF_HIR_"); var newHair = isfemaleHair ? HairListFemale.RandomElement() : HairListMale.RandomElement(); if (newHair.ObjectName.Name == entry.ObjectName.Name) { return(false); // We are not changing this } MERLog.Information($"{Path.GetFileName(hairMeshExport.FileRef.FilePath)} Changing hair mesh: {entry.ObjectName} -> {newHair.ObjectName}, object {hairMeshExport.FullPath}, class {entry.ClassName}"); var newHairMdl = PackageTools.PortExportIntoPackage(hairMeshExport.FileRef, newHair); var mdlBin = ObjectBinary.From <SkeletalMesh>(newHairMdl); obj.Value = newHairMdl.UIndex; hairMeshExport.WriteProperty(obj); // Update the materials var materials = hairMeshExport.GetProperty <ArrayProperty <ObjectProperty> >("Materials"); if (materials != null && materials.Any()) { //if (materials.Count() != 1) // Debugger.Break(); var mat = materials[0].ResolveToEntry(hairMeshExport.FileRef) as ExportEntry; if (mat != null) { mat.WriteProperty(new ObjectProperty(mdlBin.Materials[0], "Parent")); var parentMat = mdlBin.Materials[0] > 0 ? hairMeshExport.FileRef.GetUExport(mdlBin.Materials[0]) : EntryImporter.ResolveImport(hairMeshExport.FileRef.GetImport(mdlBin.Materials[0])); // Need to match child to parent params that start with HAIR var parentMatTextureParms = parentMat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues"); if (parentMatTextureParms != null) { var parentMatHairParms = parentMatTextureParms.Where(x => x.Properties.GetProp <NameProperty>("ParameterName").Value.Name.StartsWith("HAIR_")).ToList(); // Need to match child to parent params that start with HAIR var matTextureParms = mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues"); if (matTextureParms != null) { var matHairParms = matTextureParms.Where(x => x.Properties.GetProp <NameProperty>("ParameterName").Value.Name.StartsWith("HAIR_")).ToList(); // Map them foreach (var matHairParm in matHairParms) { var locName = matHairParm.Properties.GetProp <NameProperty>("ParameterName"); var matchingParent = parentMatHairParms.FirstOrDefault(x => x.Properties.GetProp <NameProperty>("ParameterName").Value == locName.Value); // Assign it if (matchingParent != null) { matHairParm.Properties.AddOrReplaceProp(matchingParent.GetProp <ObjectProperty>("ParameterValue")); } } mat.WriteProperty(matTextureParms); } } else { } } //foreach (var mat in materials) //{ // mdlBin. //} } return(true); } return(false); }
public static void CompactShaderCaches(IMEPackage mePackage) { var staticParamSetsInFile = new HashSet <StaticParameterSet>(); //figure out which MaterialShaderMaps to keep foreach (ExportEntry export in mePackage.Exports) { if (export.ClassName == "Material") { staticParamSetsInFile.Add((StaticParameterSet)ObjectBinary.From <Material>(export).SM3MaterialResource.ID); } else if (export.IsOrInheritsFrom("MaterialInstance") && export.GetProperty <BoolProperty>("bHasStaticPermutationResource")) { staticParamSetsInFile.Add(ObjectBinary.From <MaterialInstance>(export).SM3StaticParameterSet); } } var compactedShaderCache = new ShaderCache { Shaders = new OrderedMultiValueDictionary <Guid, Shader>(), MaterialShaderMaps = new OrderedMultiValueDictionary <StaticParameterSet, MaterialShaderMap>() }; //add MaterialShaderMaps foreach (ExportEntry shaderCacheExport in mePackage.Exports.Where(exp => exp.ClassName == "ShaderCache")) { var shaderCache = ObjectBinary.From <ShaderCache>(shaderCacheExport); compactedShaderCache.ShaderTypeCRCMap = shaderCache.ShaderTypeCRCMap; compactedShaderCache.VertexFactoryTypeCRCMap = shaderCache.VertexFactoryTypeCRCMap; foreach ((StaticParameterSet key, MaterialShaderMap msm) in shaderCache.MaterialShaderMaps) { if (staticParamSetsInFile.Any(sps => sps == key) && !compactedShaderCache.MaterialShaderMaps.ContainsKey(key)) { compactedShaderCache.MaterialShaderMaps.Add(key, msm); } } } //Figure out which shaders to keep var shaderGuids = new HashSet <Guid>(); foreach ((_, MaterialShaderMap materialShaderMap) in compactedShaderCache.MaterialShaderMaps) { foreach ((_, ShaderReference shaderRef) in materialShaderMap.Shaders) { shaderGuids.Add(shaderRef.Id); } foreach (MeshShaderMap meshShaderMap in materialShaderMap.MeshShaderMaps) { foreach ((_, ShaderReference shaderRef) in meshShaderMap.Shaders) { shaderGuids.Add(shaderRef.Id); } } } ExportEntry firstShaderCache = null; //add Shaders foreach (ExportEntry shaderCacheExport in mePackage.Exports.Where(exp => exp.ClassName == "ShaderCache")) { var shaderCache = ObjectBinary.From <ShaderCache>(shaderCacheExport); foreach ((Guid key, Shader shader) in shaderCache.Shaders) { if (shaderGuids.Contains(key) && !compactedShaderCache.Shaders.ContainsKey(key)) { compactedShaderCache.Shaders.Add(key, shader); } } if (firstShaderCache == null) { firstShaderCache = shaderCacheExport; } else { EntryPruner.TrashEntryAndDescendants(shaderCacheExport); } } firstShaderCache.setBinaryData(compactedShaderCache); }
public void TestFunctionParsing() { // This method is essentially test of the BytecodeEditor parser with the actual ui logic all removed GlobalTest.Init(); // Loads compressed packages, save them uncompressed. Load package, save re-compressed, compare results var packagesPath = GlobalTest.GetTestPackagesDirectory(); //var packages = Directory.GetFiles(packagesPath, "*.*", SearchOption.AllDirectories); var packages = Directory.GetFiles(packagesPath, "*.*", SearchOption.AllDirectories); foreach (var p in packages) { if (p.RepresentsPackageFilePath()) { (var game, var platform) = GlobalTest.GetExpectedTypes(p); // Use to skip //if (platform != MEPackage.GamePlatform.Xenon) continue; //if (game != MEGame.ME1) continue; Console.WriteLine($"Opening package {p}"); // Do not use package caching in tests var originalLoadedPackage = MEPackageHandler.OpenMEPackage(p, forceLoadFromDisk: true); foreach (var export in originalLoadedPackage.Exports.Where(x => x.ClassName == "Function" || x.ClassName == "State")) { //Console.WriteLine($" >> Decompiling {export.InstancedFullPath}"); var data = export.Data; var funcBin = ObjectBinary.From <UFunction>(export); //parse it out if (export.FileRef.Game == MEGame.ME3 || export.FileRef.Platform == MEPackage.GamePlatform.PS3) { var func = new Function(data, export); func.ParseFunction(); func.GetSignature(); if (export.ClassName == "Function") { var nativeBackOffset = export.FileRef.Game < MEGame.ME3 ? 7 : 6; var pos = data.Length - nativeBackOffset; string flagStr = func.GetFlags(); var nativeIndex = EndianReader.ToInt16(data, pos, export.FileRef.Endian); pos += 2; var flags = EndianReader.ToInt16(data, pos, export.FileRef.Endian); } else { //State //parse remaining var footerstartpos = 0x20 + funcBin.ScriptStorageSize; var footerdata = data.Slice(footerstartpos, (int)data.Length - (footerstartpos)); var fpos = 0; //ScriptFooterBlocks.Add(new ScriptHeaderItem("Probemask?", "??", fpos + footerstartpos) { length = 8 }); fpos += 0x8; //ScriptFooterBlocks.Add(new ScriptHeaderItem("Unknown 8 FF's", "??", fpos + footerstartpos) { length = 8 }); fpos += 0x8; //ScriptFooterBlocks.Add(new ScriptHeaderItem("Label Table Offset", "??", fpos + footerstartpos) { length = 2 }); fpos += 0x2; var stateFlagsBytes = footerdata.Slice(fpos, 0x4); var stateFlags = (StateFlags)EndianReader.ToInt32(stateFlagsBytes, 0, export.FileRef.Endian); var names = stateFlags.ToString().Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); fpos += 0x4; var numMappedFunctions = EndianReader.ToInt32(footerdata, fpos, export.FileRef.Endian); fpos += 4; for (int i = 0; i < numMappedFunctions; i++) { var name = EndianReader.ToInt32(footerdata, fpos, export.FileRef.Endian); var uindex = EndianReader.ToInt32(footerdata, fpos + 8, export.FileRef.Endian); var funcMapText = $"{export.FileRef.GetNameEntry(name)} => {export.FileRef.GetEntry(uindex)?.FullPath}()"; fpos += 12; } } } else if (export.FileRef.Game == MEGame.ME1 || export.FileRef.Game == MEGame.ME2) { //Header int pos = 16; var nextItemCompilingChain = EndianReader.ToInt32(data, pos, export.FileRef.Endian); //ScriptHeaderBlocks.Add(new ScriptHeaderItem("Next item in loading chain", nextItemCompilingChain, pos, nextItemCompilingChain > 0 ? export : null)); pos += 8; nextItemCompilingChain = EndianReader.ToInt32(data, pos, export.FileRef.Endian); //ScriptHeaderBlocks.Add(new ScriptHeaderItem("Children Probe Start", nextItemCompilingChain, pos, nextItemCompilingChain > 0 ? export : null)); pos += 8; var line = EndianReader.ToInt32(data, pos, export.FileRef.Endian); //ScriptHeaderBlocks.Add(new ScriptHeaderItem("Line", EndianReader.ToInt32(data, pos, export.FileRef.Endian), pos)); pos += 4; //EndianReader.ToInt32(data, pos, export.FileRef.Endian) //ScriptHeaderBlocks.Add(new ScriptHeaderItem("TextPos", EndianReader.ToInt32(data, pos, export.FileRef.Endian), pos)); pos += 4; int scriptSize = EndianReader.ToInt32(data, pos, export.FileRef.Endian); //ScriptHeaderBlocks.Add(new ScriptHeaderItem("Script Size", scriptSize, pos)); pos += 4; var BytecodeStart = pos; var func = export.ClassName == "State" ? UE3FunctionReader.ReadState(export, data) : UE3FunctionReader.ReadFunction(export, data); func.Decompile(new TextBuilder(), false); //parse bytecode bool defined = func.HasFlag("Defined"); //if (defined) //{ // DecompiledScriptBlocks.Add(func.FunctionSignature + " {"); //} //else //{ // //DecompiledScriptBlocks.Add(func.FunctionSignature); //} for (int i = 0; i < func.Statements.statements.Count; i++) { Statement s = func.Statements.statements[i]; s.SetPaddingForScriptSize(scriptSize); if (s.Reader != null && i == 0) { //Add tokens read from statement. All of them point to the same reader, so just do only the first one. s.Reader.ReadTokens.Select(x => x.ToBytecodeSingularToken(pos)).OrderBy(x => x.StartPos); } } //if (defined) //{ // DecompiledScriptBlocks.Add("}"); //} //Footer pos = data.Length - (func.HasFlag("Net") ? 17 : 15); string flagStr = func.GetFlags(); //ScriptFooterBlocks.Add(new ScriptHeaderItem("Native Index", EndianReader.ToInt16(data, pos, export.FileRef.Endian), pos)); pos += 2; //ScriptFooterBlocks.Add(new ScriptHeaderItem("Operator Precedence", data[pos], pos)); pos++; int functionFlags = EndianReader.ToInt32(data, pos, export.FileRef.Endian); //ScriptFooterBlocks.Add(new ScriptHeaderItem("Flags", $"0x{functionFlags:X8} {flagStr}", pos)); pos += 4; //if ((functionFlags & func._flagSet.GetMask("Net")) != 0) //{ //ScriptFooterBlocks.Add(new ScriptHeaderItem("Unknown 1 (RepOffset?)", EndianReader.ToInt16(data, pos, export.FileRef.Endian), pos)); //pos += 2; //} int friendlyNameIndex = EndianReader.ToInt32(data, pos, export.FileRef.Endian); var friendlyName = export.FileRef.GetNameEntry(friendlyNameIndex); //ScriptFooterBlocks.Add(new ScriptHeaderItem("Friendly Name", Pcc.GetNameEntry(friendlyNameIndex), pos) { length = 8 }); pos += 8; //ME1Explorer.Unreal.Classes.Function func = new ME1Explorer.Unreal.Classes.Function(data, export.FileRef as ME1Package); //try //{ // Function_TextBox.Text = func.ToRawText(); //} //catch (Exception e) //{ // Function_TextBox.Text = "Error parsing function: " + e.Message; //} } else { //Function_TextBox.Text = "Parsing UnrealScript Functions for this game is not supported."; } } //} } } }
private void ReadMaterial(ExportEntry export, PackageCache assetCache = null) { if (export.ClassName == "Material") { var parsedMaterial = ObjectBinary.From <Material>(export); StaticParameterSet = (StaticParameterSet)parsedMaterial.SM3MaterialResource.ID; foreach (var v in parsedMaterial.SM3MaterialResource.UniformExpressionTextures) { IEntry tex = export.FileRef.GetEntry(v.value); if (tex != null) { Textures.Add(tex); } } } else if (export.ClassName == "RvrEffectsMaterialUser") { var props = export.GetProperties(); if (export.GetProperty <ObjectProperty>("m_pBaseMaterial") is ObjectProperty baseProp) { // This is an instance... maybe? if (baseProp.Value > 0) { // Local export ReadMaterial(export.FileRef.GetUExport(baseProp.Value)); } else { ImportEntry ie = export.FileRef.GetImport(baseProp.Value); var externalEntry = EntryImporter.ResolveImport(ie, null, assetCache); if (externalEntry != null) { ReadMaterial(externalEntry); } } } } else if (export.ClassName == "MaterialInstanceConstant") { if (ObjectBinary.From(export) is MaterialInstance matInstBin) { StaticParameterSet = matInstBin.SM3StaticParameterSet; } //Read Local if (export.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> textureparams) { foreach (var param in textureparams) { var paramValue = param.GetProp <ObjectProperty>("ParameterValue"); var texntry = export.FileRef.GetEntry(paramValue.Value); if (texntry?.ClassName == "Texture2D" && !Textures.Contains(texntry)) { Textures.Add(texntry); } } } if (export.GetProperty <ArrayProperty <ObjectProperty> >("ReferencedTextures") is ArrayProperty <ObjectProperty> textures) { foreach (var obj in textures) { var texntry = export.FileRef.GetEntry(obj.Value); if (texntry.ClassName == "Texture2D" && !Textures.Contains(texntry)) { Textures.Add(texntry); } } } //Read parent if (export.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentObjProp) { // This is an instance... maybe? if (parentObjProp.Value > 0) { // Local export ReadMaterial(export.FileRef.GetUExport(parentObjProp.Value)); } else { ImportEntry ie = export.FileRef.GetImport(parentObjProp.Value); var externalEntry = ModelPreview.FindExternalAsset(ie, null, null); if (externalEntry != null) { ReadMaterial(externalEntry); } } } } }
public void ConvertTo(MEGame newGame) { MEGame oldGame = Game; var prePropBinary = new List <byte[]>(ExportCount); var propCollections = new List <PropertyCollection>(ExportCount); var postPropBinary = new List <ObjectBinary>(ExportCount); if (oldGame == MEGame.ME1 && newGame != MEGame.ME1) { int idx = names.IndexOf("BIOC_Base"); if (idx >= 0) { names[idx] = "SFXGame"; } } else if (newGame == MEGame.ME1) { int idx = names.IndexOf("SFXGame"); if (idx >= 0) { names[idx] = "BIOC_Base"; } } //fix up Default_ imports if (newGame == MEGame.ME3) { using (IMEPackage core = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.cookedPath, "Core.pcc"))) using (IMEPackage engine = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.cookedPath, "Engine.pcc"))) using (IMEPackage sfxGame = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.cookedPath, "SFXGame.pcc"))) { foreach (ImportEntry defImp in imports.Where(imp => imp.ObjectName.Name.StartsWith("Default_")).ToList()) { string packageName = defImp.FullPath.Split('.')[0]; IMEPackage pck = packageName == "Core" ? core : packageName == "Engine" ? engine : packageName == "SFXGame" ? sfxGame : null; if (pck != null && pck.Exports.FirstOrDefault(exp => exp.ObjectName == defImp.ObjectName) is ExportEntry defExp) { var impChildren = defImp.GetChildren(); var expChildren = defExp.GetChildren(); foreach (IEntry expChild in expChildren) { if (impChildren.FirstOrDefault(imp => imp.ObjectName == expChild.ObjectName) is ImportEntry matchingImp) { impChildren.Remove(matchingImp); } else { AddImport(new ImportEntry(this) { idxLink = defImp.UIndex, ClassName = expChild.ClassName, ObjectName = expChild.ObjectName, PackageFile = defImp.PackageFile }); } } foreach (IEntry impChild in impChildren) { EntryPruner.TrashEntries(this, impChild.GetAllDescendants().Prepend(impChild)); } } } } } //purge MaterialExpressions if (newGame == MEGame.ME3) { var entriesToTrash = new List <IEntry>(); foreach (ExportEntry mat in exports.Where(exp => exp.ClassName == "Material").ToList()) { entriesToTrash.AddRange(mat.GetAllDescendants()); } EntryPruner.TrashEntries(this, entriesToTrash.ToHashSet()); } EntryPruner.TrashIncompatibleEntries(this, oldGame, newGame); foreach (ExportEntry export in exports) { //convert stack, or just get the pre-prop binary if no stack prePropBinary.Add(ExportBinaryConverter.ConvertPrePropBinary(export, newGame)); if (export.ClassName == "Class") { propCollections.Add(null); } else { //read in all properties in the old format, and remove ones that are incompatible with newGame propCollections.Add(EntryPruner.RemoveIncompatibleProperties(this, export.GetProperties(), export.ClassName, newGame)); } //convert binary data postPropBinary.Add(ExportBinaryConverter.ConvertPostPropBinary(export, newGame)); //writes header in whatever format is correct for newGame export.RegenerateHeader(newGame, true); } Game = newGame; for (int i = 0; i < exports.Count; i++) { var newData = new MemoryStream(); newData.WriteFromBuffer(prePropBinary[i]); //write back properties in new format propCollections[i]?.WriteTo(newData, this); postPropBinary[i].WriteTo(newData, this, exports[i].DataOffset + exports[i].propsEnd()); //should do this again during Save to get offsets correct //might not matter though exports[i].Data = newData.ToArray(); } if (newGame == MEGame.ME3) { //change all materials to default material, but try to preserve diff and norm textures using var resourcePCC = MEPackageHandler.OpenME3Package(App.CustomResourceFilePath(MEGame.ME3)); var normDiffMat = resourcePCC.Exports.First(exp => exp.ObjectName == "NormDiffMaterial"); foreach (ExportEntry mat in exports.Where(exp => exp.ClassName == "Material" || exp.ClassName == "MaterialInstanceConstant")) { UIndex[] textures = Array.Empty <UIndex>(); if (mat.ClassName == "Material") { textures = ObjectBinary.From <Material>(mat).SM3MaterialResource.UniformExpressionTextures; } else if (mat.GetProperty <BoolProperty>("bHasStaticPermutationResource")?.Value == true) { textures = ObjectBinary.From <MaterialInstance>(mat).SM3StaticPermutationResource.UniformExpressionTextures; } else if (mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> texParams) { textures = texParams.Select(structProp => new UIndex(structProp.GetProp <ObjectProperty>("ParameterValue")?.Value ?? 0)).ToArray(); } else if (mat.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentProp && GetEntry(parentProp.Value) is ExportEntry parent && parent.ClassName == "Material") { textures = ObjectBinary.From <Material>(parent).SM3MaterialResource.UniformExpressionTextures; } EntryImporter.ReplaceExportDataWithAnother(normDiffMat, mat); int norm = 0; int diff = 0; foreach (UIndex texture in textures) { if (GetEntry(texture) is IEntry tex) { if (diff == 0 && tex.ObjectName.Name.Contains("diff", StringComparison.OrdinalIgnoreCase)) { diff = texture; } else if (norm == 0 && tex.ObjectName.Name.Contains("norm", StringComparison.OrdinalIgnoreCase)) { norm = texture; } } } if (diff == 0) { diff = EntryImporter.GetOrAddCrossImportOrPackage("EngineMaterials.DefaultDiffuse", resourcePCC, this).UIndex; } var matBin = ObjectBinary.From <Material>(mat); matBin.SM3MaterialResource.UniformExpressionTextures = new UIndex[] { norm, diff }; mat.setBinaryData(matBin.ToBytes(this)); mat.Class = imports.First(imp => imp.ObjectName == "Material"); } } if (newGame != MEGame.ME3) { foreach (ExportEntry texport in exports.Where(exp => exp.IsTexture())) { texport.WriteProperty(new BoolProperty(true, "NeverStream")); } } else if (exports.Any(exp => exp.IsTexture() && Texture2D.GetTexture2DMipInfos(exp, null) .Any(mip => mip.storageType == StorageTypes.pccLZO || mip.storageType == StorageTypes.pccZlib))) { //ME3 can't deal with compressed textures in a pcc, so we'll need to stuff them into a tfc string tfcName = Path.GetFileNameWithoutExtension(FilePath); using var tfc = File.OpenWrite(Path.ChangeExtension(FilePath, "tfc")); Guid tfcGuid = Guid.NewGuid(); tfc.WriteGuid(tfcGuid); foreach (ExportEntry texport in exports.Where(exp => exp.IsTexture())) { List <Texture2DMipInfo> mips = Texture2D.GetTexture2DMipInfos(texport, null); var offsets = new List <int>(); foreach (Texture2DMipInfo mipInfo in mips) { if (mipInfo.storageType == StorageTypes.pccLZO || mipInfo.storageType == StorageTypes.pccZlib) { offsets.Add((int)tfc.Position); byte[] mip; if (mipInfo.storageType == StorageTypes.pccLZO) { mip = TextureCompression.CompressTexture(Texture2D.GetTextureData(mipInfo), StorageTypes.extZlib); } else { mip = Texture2D.GetTextureData(mipInfo, false); } tfc.WriteFromBuffer(mip); } } offsets.Add((int)tfc.Position); texport.setBinaryData(ExportBinaryConverter.ConvertTexture2D(texport, Game, offsets, StorageTypes.extZlib)); texport.WriteProperty(new NameProperty(tfcName, "TextureFileCacheName")); texport.WriteProperty(tfcGuid.ToGuidStructProp("TFCFileGuid")); } } }
public static bool RandomizeExport(ExportEntry exp, RandomizationOption option) { if (!CanRandomize(exp)) { return(false); } //if (exp.UIndex != 1999) return false; try { //Log.Information($"[{Path.GetFileNameWithoutExtension(exp.FileRef.FilePath)}] Randomizing FaceFX export {exp.UIndex}"); //var d = exp.Data; var animSet = ObjectBinary.From <FaceFXAnimSet>(exp); for (int i = 0; i < animSet.Lines.Count(); i++) { var faceFxline = animSet.Lines[i]; //if (true) bool randomizedBoneList = false; if (ThreadSafeRandom.Next(10 - (int)option.SliderValue) == 0) { //Randomize the names used for animation faceFxline.AnimationNames.Shuffle(); randomizedBoneList = true; } if (!randomizedBoneList || ThreadSafeRandom.Next(16 - (int)option.SliderValue) == 0) // if bonelist was shuffled, or if 1 in 16 chance while it was also shuffled. { //Randomize the points for (int j = 0; j < faceFxline.Points.Count; j++) { bool isLast = j == faceFxline.Points.Count - 1; var currentPoint = faceFxline.Points[j]; switch (option.SliderValue) { case 1: //A few broken bones currentPoint.weight += ThreadSafeRandom.NextFloat(-.25, .25); break; case 2: //A significant option.SliderValue of broken bones currentPoint.weight += ThreadSafeRandom.NextFloat(-.5, .5); break; case 3: //That's not how the face is supposed to work if (ThreadSafeRandom.Next(5) == 0) { currentPoint.weight = ThreadSafeRandom.NextFloat(-3, 3); } else if (isLast && ThreadSafeRandom.Next(3) == 0) { currentPoint.weight = ThreadSafeRandom.NextFloat(.7, .7); } else { currentPoint.weight *= 8; } break; case 4: //:O if (ThreadSafeRandom.Next(12) == 0) { currentPoint.weight = ThreadSafeRandom.NextFloat(-5, 5); } else if (isLast && ThreadSafeRandom.Next(2) == 0) { currentPoint.weight = ThreadSafeRandom.NextFloat(.9, .9); } else { currentPoint.weight *= 5; } //Debug.WriteLine(currentPoint.weight); break; case 5: //Utter madness currentPoint.weight = ThreadSafeRandom.NextFloat(-10, 10); break; default: Debugger.Break(); break; } //if (currentPoint.weight == 0) //Debug.WriteLine($"Weight is 0, {faceFxline.NameAsString}, exp {exp.UIndex} {exp.FullPath}"); faceFxline.Points[j] = currentPoint; //Reassign the struct } } //Debugging only: Get list of all animation names //for (int j = 0; j < faceFxline.animations.Length; j++) //{ // var animationName = animSet.Header.Names[faceFxline.animations[j].index]; //animation name // faceFxBoneNames.Add(animationName); //} } //var dataBefore = exp.Data; exp.WriteBinary(animSet); //var dataAfter = exp.Data; //if (dataBefore.SequenceEqual(dataAfter)) //{ //Debugger.Break(); //} //else //{ // Log.Information($"[{Path.GetFileNameWithoutExtension(exp.FileRef.FilePath)}] Randomized FaceFX for export " + exp.UIndex); // } return(true); } catch (Exception e) { //Do nothing for now. Log.Error("AnimSet error! " + App.FlattenException((e))); } return(false); }
public static void WritePropertyAndBinary(this ExportEntry export, Property prop, ObjectBinary binary) { var props = export.GetProperties(); props.AddOrReplaceProp(prop); export.WritePropertiesAndBinary(props, binary); }
public static void setBinaryData(this ExportEntry export, ObjectBinary bin) => export.setBinaryData(bin.ToBytes(export.FileRef, export.DataOffset + export.propsEnd()));
public static PropertyCollection GetSequenceObjectDefaults(IMEPackage pcc, ClassInfo info) { MEGame game = pcc.Game; PropertyCollection defaults = new PropertyCollection(); if (info.ClassName == "Sequence") { defaults.Add(new ArrayProperty <ObjectProperty>("SequenceObjects")); } else if (!info.IsA(SequenceVariableName, game)) { ArrayProperty <StructProperty> varLinksProp = null; ArrayProperty <StructProperty> outLinksProp = null; ArrayProperty <StructProperty> eventLinksProp = null; ArrayProperty <StructProperty> inLinksProp = null; Dictionary <string, ClassInfo> classes = UnrealObjectInfo.GetClasses(game); try { ClassInfo classInfo = info; while (classInfo != null && (varLinksProp is null || outLinksProp is null || eventLinksProp is null || game == MEGame.ME1 && inLinksProp is null)) { string filepath = Path.Combine(MEDirectories.GetBioGamePath(game), classInfo.pccPath); Stream loadStream = null; if (File.Exists(classInfo.pccPath)) { loadStream = new MemoryStream(File.ReadAllBytes(classInfo.pccPath)); } else if (classInfo.pccPath == UnrealObjectInfo.Me3ExplorerCustomNativeAdditionsName) { loadStream = Utilities.GetCustomAppResourceStream(game); } else if (File.Exists(filepath)) { loadStream = new MemoryStream(File.ReadAllBytes(filepath)); } else if (game == MEGame.ME1) { filepath = Path.Combine(ME1Directory.DefaultGamePath, classInfo.pccPath); //for files from ME1 DLC if (File.Exists(filepath)) { loadStream = new MemoryStream(File.ReadAllBytes(filepath)); } } if (loadStream != null) { using IMEPackage importPCC = MEPackageHandler.OpenMEPackageFromStream(loadStream); ExportEntry classExport = importPCC.GetUExport(classInfo.exportIndex); UClass classBin = ObjectBinary.From <UClass>(classExport); ExportEntry classDefaults = importPCC.GetUExport(classBin.Defaults); foreach (var prop in classDefaults.GetProperties()) { if (varLinksProp == null && prop.Name == "VariableLinks" && prop is ArrayProperty <StructProperty> vlp) { varLinksProp = vlp; //relink ExpectedType foreach (StructProperty varLink in varLinksProp) { if (varLink.GetProp <ObjectProperty>("ExpectedType") is ObjectProperty expectedTypeProp && importPCC.TryGetEntry(expectedTypeProp.Value, out IEntry expectedVar) && EntryImporterExtended.EnsureClassIsInFile(pcc, expectedVar.ObjectName) is IEntry portedExpectedVar) { expectedTypeProp.Value = portedExpectedVar.UIndex; } } } if (outLinksProp == null && prop.Name == "OutputLinks" && prop is ArrayProperty <StructProperty> olp) { outLinksProp = olp; } if (eventLinksProp == null && prop.Name == "EventLinks" && prop is ArrayProperty <StructProperty> elp) { eventLinksProp = elp; //relink ExpectedType foreach (StructProperty eventLink in eventLinksProp) { if (eventLink.GetProp <ObjectProperty>("ExpectedType") is ObjectProperty expectedTypeProp && importPCC.TryGetEntry(expectedTypeProp.Value, out IEntry expectedVar) && EntryImporterExtended.EnsureClassIsInFile(pcc, expectedVar.ObjectName) is IEntry portedExpectedVar) { expectedTypeProp.Value = portedExpectedVar.UIndex; } } } if (game == MEGame.ME1 && inLinksProp is null && prop.Name == "InputLinks" && prop is ArrayProperty <StructProperty> ilp) { inLinksProp = ilp; } } } classes.TryGetValue(classInfo.baseClass, out classInfo); } } catch { // ignored } if (varLinksProp != null) { defaults.Add(varLinksProp); } if (outLinksProp != null) { defaults.Add(outLinksProp); } if (eventLinksProp != null) { defaults.Add(eventLinksProp); } if (inLinksProp != null) { defaults.Add(inLinksProp); } //remove links if empty if (defaults.GetProp <ArrayProperty <StructProperty> >("OutputLinks") is { } outLinks&& outLinks.IsEmpty()) { defaults.Remove(outLinks); } if (defaults.GetProp <ArrayProperty <StructProperty> >("VariableLinks") is { } varLinks&& varLinks.IsEmpty()) { defaults.Remove(varLinks); } if (defaults.GetProp <ArrayProperty <StructProperty> >("EventLinks") is { } eventLinks&& eventLinks.IsEmpty()) { defaults.Remove(eventLinks); } if (defaults.GetProp <ArrayProperty <StructProperty> >("InputLinks") is { } inputLinks&& inputLinks.IsEmpty()) { defaults.Remove(inputLinks); } } int objInstanceVersion = UnrealObjectInfo.getSequenceObjectInfo(game, info.ClassName)?.ObjInstanceVersion ?? 1; defaults.Add(new IntProperty(objInstanceVersion, "ObjInstanceVersion")); return(defaults); }
public static Dictionary <IEntry, List <string> > GetEntriesThatReferenceThisOne(this IEntry baseEntry) { var result = new Dictionary <IEntry, List <string> >(); int baseUIndex = baseEntry.UIndex; foreach (ExportEntry exp in baseEntry.FileRef.Exports) { try { if (exp == baseEntry) { continue; } //find header references if (exp.Archetype == baseEntry) { result.AddToListAt(exp, "Header: Archetype"); } if (exp.Class == baseEntry) { result.AddToListAt(exp, "Header: Class"); } if (exp.SuperClass == baseEntry) { result.AddToListAt(exp, "Header: SuperClass"); } if (exp.HasComponentMap && exp.ComponentMap.Any(kvp => kvp.Value == baseUIndex)) { result.AddToListAt(exp, "Header: ComponentMap"); } //find stack references if (exp.HasStack) { if (exp.Data is byte[] data && (baseUIndex == BitConverter.ToInt32(data, 0) || baseUIndex == BitConverter.ToInt32(data, 4))) { result.AddToListAt(exp, "Stack"); } } else if (exp.TemplateOwnerClassIdx is var toci && toci >= 0 && baseUIndex == BitConverter.ToInt32(exp.Data, toci)) { result.AddToListAt(exp, $"TemplateOwnerClass (Data offset 0x{toci:X})"); } //find property references findPropertyReferences(exp.GetProperties(), exp, "Property:"); //find binary references if (!exp.IsDefaultObject && ObjectBinary.From(exp) is ObjectBinary objBin) { List <(UIndex, string)> indices = objBin.GetUIndexes(exp.FileRef.Game); foreach ((UIndex uIndex, string propName) in indices) { if (uIndex == baseUIndex) { result.AddToListAt(exp, $"(Binary prop: {propName})"); } } } } catch (Exception e) when(!CoreLib.IsDebug) { result.AddToListAt(exp, $"Exception occurred while reading this export: {e.Message}"); } } return(result); void findPropertyReferences(PropertyCollection props, ExportEntry exp, string prefix = "") { foreach (Property prop in props) { switch (prop) { case ObjectProperty objectProperty: if (objectProperty.Value == baseUIndex) { result.AddToListAt(exp, $"{prefix} {objectProperty.Name}"); } break; case DelegateProperty delegateProperty: if (delegateProperty.Value.Object == baseUIndex) { result.AddToListAt(exp, $"{prefix} {delegateProperty.Name}"); } break; case StructProperty structProperty: findPropertyReferences(structProperty.Properties, exp, $"{prefix} {structProperty.Name}:"); break; case ArrayProperty <ObjectProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { ObjectProperty objProp = arrayProperty[i]; if (objProp.Value == baseUIndex) { result.AddToListAt(exp, $"{prefix} {arrayProperty.Name}[{i}]"); } } break; case ArrayProperty <StructProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { StructProperty structProp = arrayProperty[i]; findPropertyReferences(structProp.Properties, exp, $"{prefix} {arrayProperty.Name}[{i}]:"); } break; } } } }
public static Dictionary <IEntry, List <string> > GetEntriesThatReferenceThisOne(this IEntry baseEntry) { var result = new Dictionary <IEntry, List <string> >(); int baseUIndex = baseEntry.UIndex; foreach (ExportEntry exp in baseEntry.FileRef.Exports) { if (exp == baseEntry) { continue; } //find header references if (exp.idxArchtype == baseUIndex) { result.AddToListAt(exp, "Header: Archetype"); } if (exp.idxClass == baseUIndex) { result.AddToListAt(exp, "Header: Class"); } if (exp.idxSuperClass == baseUIndex) { result.AddToListAt(exp, "Header: SuperClass"); } if (exp.HasComponentMap && exp.ComponentMap.Any(kvp => kvp.Value == baseUIndex)) { result.AddToListAt(exp, "Header: ComponentMap"); } //find stack references if (exp.HasStack && exp.Data is byte[] data && (baseUIndex == BitConverter.ToInt32(data, 0) || baseUIndex == BitConverter.ToInt32(data, 4))) { result.AddToListAt(exp, "Stack"); } //find property references findPropertyReferences(exp.GetProperties(), exp, "Property:"); //find binary references if (ObjectBinary.From(exp) is ObjectBinary objBin) { List <(UIndex, string)> indices = objBin.GetUIndexes(exp.FileRef.Game); foreach ((UIndex uIndex, string propName) in indices) { if (uIndex == baseUIndex) { result.AddToListAt(exp, $"(Binary prop: {propName})"); } } } } return(result); void findPropertyReferences(PropertyCollection props, ExportEntry exp, string prefix = "") { foreach (UProperty prop in props) { switch (prop) { case ObjectProperty objectProperty: if (objectProperty.Value == baseUIndex) { result.AddToListAt(exp, $"{prefix} {objectProperty.Name}"); } break; case DelegateProperty delegateProperty: if (delegateProperty.Value.Object == baseUIndex) { result.AddToListAt(exp, $"{prefix} {delegateProperty.Name}"); } break; case StructProperty structProperty: findPropertyReferences(structProperty.Properties, exp, $"{prefix} {structProperty.Name}:"); break; case ArrayProperty <ObjectProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { ObjectProperty objProp = arrayProperty[i]; if (objProp.Value == baseUIndex) { result.AddToListAt(exp, $"{prefix} {arrayProperty.Name}[{i}]"); } } break; case ArrayProperty <StructProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { StructProperty structProp = arrayProperty[i]; findPropertyReferences(structProp.Properties, exp, $"{prefix} {arrayProperty.Name}[{i}]:"); } break; } } } }
public static int ReplaceAllReferencesToThisOne(this IEntry baseEntry, IEntry replacementEntry) { int rcount = 0; int selectedEntryUIndex = baseEntry.UIndex; int replacementUIndex = replacementEntry.UIndex; var references = baseEntry.GetEntriesThatReferenceThisOne(); foreach ((IEntry entry, List <string> propsList) in references) { if (entry is ExportEntry exp) { if (propsList.Any(l => l.StartsWith("Property:"))) { var newprops = replacePropertyReferences(exp.GetProperties(), selectedEntryUIndex, replacementUIndex, ref rcount); exp.WriteProperties(newprops); } else { if (propsList.Any(l => l.StartsWith("(Binary prop:")) && !exp.IsDefaultObject && ObjectBinary.From(exp) is ObjectBinary objBin) { List <(UIndex, string)> indices = objBin.GetUIndexes(exp.FileRef.Game); foreach ((UIndex uIndex, _) in indices) { if (uIndex.value == selectedEntryUIndex) { uIndex.value = replacementUIndex; rcount++; } } //script relinking is not covered by standard binary relinking if (objBin is UStruct uStruct && uStruct.ScriptBytes.Length > 0) { if (exp.Game == MEGame.ME3) { (List <Token> tokens, _) = Bytecode.ParseBytecode(uStruct.ScriptBytes, exp); foreach (Token token in tokens) { foreach ((int pos, int type, int value) in token.inPackageReferences) { switch (type) { case Token.INPACKAGEREFTYPE_ENTRY when value == selectedEntryUIndex: uStruct.ScriptBytes.OverwriteRange(pos, BitConverter.GetBytes(replacementUIndex)); rcount++; break; } } } } else { var func = entry.ClassName == "State" ? UE3FunctionReader.ReadState(exp) : UE3FunctionReader.ReadFunction(exp); func.Decompile(new TextBuilder(), false); foreach ((long position, IEntry ent) in func.EntryReferences) { if (ent.UIndex == selectedEntryUIndex && position < uStruct.ScriptBytes.Length) { uStruct.ScriptBytes.OverwriteRange((int)position, BitConverter.GetBytes(replacementUIndex)); rcount++; } } } } exp.WriteBinary(objBin); } } } } return(rcount);
public static void ConvertTo(this MEPackage package, MEGame newGame, string tfcPath = null, bool preserveMaterialInstances = false) { MEGame oldGame = package.Game; var prePropBinary = new List <byte[]>(package.ExportCount); var propCollections = new List <PropertyCollection>(package.ExportCount); var postPropBinary = new List <ObjectBinary>(package.ExportCount); if (oldGame == MEGame.ME1 && newGame != MEGame.ME1) { int idx = package.Names.IndexOf("BIOC_Base"); if (idx >= 0) { package.replaceName(idx, "SFXGame"); } } else if (newGame == MEGame.ME1) { int idx = package.Names.IndexOf("SFXGame"); if (idx >= 0) { package.replaceName(idx, "BIOC_Base"); } } //fix up Default_ package.Imports if (newGame == MEGame.ME3) { using IMEPackage core = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "Core.pcc")); using IMEPackage engine = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "Engine.pcc")); using IMEPackage sfxGame = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "SFXGame.pcc")); foreach (ImportEntry defImp in package.Imports.Where(imp => imp.ObjectName.Name.StartsWith("Default_")).ToList()) { string packageName = defImp.FullPath.Split('.')[0]; IMEPackage pck = packageName switch { "Core" => core, "Engine" => engine, "SFXGame" => sfxGame, _ => null }; if (pck != null && pck.Exports.FirstOrDefault(exp => exp.ObjectName == defImp.ObjectName) is ExportEntry defExp) { List <IEntry> impChildren = defImp.GetChildren(); List <IEntry> expChildren = defExp.GetChildren(); foreach (IEntry expChild in expChildren) { if (impChildren.FirstOrDefault(imp => imp.ObjectName == expChild.ObjectName) is ImportEntry matchingImp) { impChildren.Remove(matchingImp); } else { package.AddImport(new ImportEntry(package) { idxLink = defImp.UIndex, ClassName = expChild.ClassName, ObjectName = expChild.ObjectName, PackageFile = defImp.PackageFile }); } } foreach (IEntry impChild in impChildren) { EntryPruner.TrashEntries(package, impChild.GetAllDescendants().Prepend(impChild)); } } } } //purge MaterialExpressions if (newGame == MEGame.ME3) { var entriesToTrash = new List <IEntry>(); foreach (ExportEntry mat in package.Exports.Where(exp => exp.ClassName == "Material").ToList()) { entriesToTrash.AddRange(mat.GetAllDescendants()); } EntryPruner.TrashEntries(package, entriesToTrash.ToHashSet()); } EntryPruner.TrashIncompatibleEntries(package, oldGame, newGame); foreach (ExportEntry export in package.Exports) { //convert stack, or just get the pre-prop binary if no stack prePropBinary.Add(ExportBinaryConverter.ConvertPrePropBinary(export, newGame)); PropertyCollection props = export.ClassName == "Class" ? null : EntryPruner.RemoveIncompatibleProperties(package, export.GetProperties(), export.ClassName, newGame); propCollections.Add(props); //convert binary data postPropBinary.Add(ExportBinaryConverter.ConvertPostPropBinary(export, newGame, props)); //writes header in whatever format is correct for newGame export.RegenerateHeader(newGame, true); } package.setGame(newGame); for (int i = 0; i < package.Exports.Count; i++) { package.Exports[i].WritePrePropsAndPropertiesAndBinary(prePropBinary[i], propCollections[i], postPropBinary[i]); } if (newGame != MEGame.ME3) //Fix Up Textures before Materials { foreach (ExportEntry texport in package.Exports.Where(exp => exp.IsTexture())) { texport.WriteProperty(new BoolProperty(true, "NeverStream")); } } else if (package.Exports.Any(exp => exp.IsTexture() && Texture2D.GetTexture2DMipInfos(exp, null) .Any(mip => mip.storageType == StorageTypes.pccLZO || mip.storageType == StorageTypes.pccZlib))) { //ME3 can't deal with compressed textures in a pcc, so we'll need to stuff them into a tfc tfcPath ??= Path.ChangeExtension(package.FilePath, "tfc"); string tfcName = Path.GetFileNameWithoutExtension(tfcPath); using var tfc = new FileStream(tfcPath, FileMode.OpenOrCreate, FileAccess.ReadWrite); Guid tfcGuid; if (tfc.Length >= 16) { tfcGuid = tfc.ReadGuid(); tfc.SeekEnd(); } else { tfcGuid = Guid.NewGuid(); tfc.WriteGuid(tfcGuid); } foreach (ExportEntry texport in package.Exports.Where(exp => exp.IsTexture())) { List <Texture2DMipInfo> mips = Texture2D.GetTexture2DMipInfos(texport, null); var offsets = new List <int>(); foreach (Texture2DMipInfo mipInfo in mips) { if (mipInfo.storageType == StorageTypes.pccLZO || mipInfo.storageType == StorageTypes.pccZlib) { offsets.Add((int)tfc.Position); byte[] mip = mipInfo.storageType == StorageTypes.pccLZO ? TextureCompression.CompressTexture(Texture2D.GetTextureData(mipInfo, texport.Game), StorageTypes.extZlib) : Texture2D.GetTextureData(mipInfo, texport.Game, decompress: false); tfc.WriteFromBuffer(mip); } } offsets.Add((int)tfc.Position); texport.WriteBinary(ExportBinaryConverter.ConvertTexture2D(texport, package.Game, offsets, StorageTypes.extZlib)); texport.WriteProperty(new NameProperty(tfcName, "TextureFileCacheName")); texport.WriteProperty(tfcGuid.ToGuidStructProp("TFCFileGuid")); } } if (oldGame == MEGame.ME3 && newGame != MEGame.ME3) { int idx = package.Names.IndexOf("location"); if (idx >= 0) { package.replaceName(idx, "Location"); } } else if (newGame == MEGame.ME3) { int idx = package.Names.IndexOf("Location"); if (idx >= 0) { package.replaceName(idx, "location"); } } if (newGame == MEGame.ME3) //Special handling where materials have been ported between games. { //change all materials to default material, but try to preserve diff and norm textures using var resourcePCC = MEPackageHandler.OpenMEPackageFromStream(ME3ExplorerCoreUtilities.GetCustomAppResourceStream(MEGame.ME3)); var defaultmaster = resourcePCC.Exports.First(exp => exp.ObjectName == "NormDiffMaterial"); var materiallist = package.Exports.Where(exp => exp.ClassName == "Material" || exp.ClassName == "MaterialInstanceConstant").ToList(); foreach (var mat in materiallist) { Debug.WriteLine($"Fixing up {mat.FullPath}"); var masterMat = defaultmaster; var hasDefaultMaster = true; UIndex[] textures = Array.Empty <UIndex>(); if (mat.ClassName == "Material") { textures = ObjectBinary.From <Material>(mat).SM3MaterialResource.UniformExpressionTextures; switch (mat.FullPath) { case "BioT_Volumetric.LAG_MM_Volumetric": case "BioT_Volumetric.LAG_MM_FalloffSphere": case "BioT_LevelMaster.Materials.Opaque_MM": case "BioT_LevelMaster.Materials.GUI_Lit_MM": case "BioT_LevelMaster.Materials.Signage.MM_GUIMaster_Emissive": case "BioT_LevelMaster.Materials.Signage.MM_GUIMaster_Emissive_Fallback": case "BioT_LevelMaster.Materials.Opaque_Standard_MM": case "BioT_LevelMaster.Tech_Inset_MM": case "BioT_LevelMaster.Tech_Border_MM": case "BioT_LevelMaster.Brushed_Metal": masterMat = resourcePCC.Exports.First(exp => exp.FullPath == mat.FullPath); hasDefaultMaster = false; break; default: break; } } else if (mat.GetProperty <BoolProperty>("bHasStaticPermutationResource")?.Value == true) { if (mat.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentProp && package.GetEntry(parentProp.Value) is IEntry parent && parent.ClassName == "Material") { switch (parent.FullPath) { case "BioT_LevelMaster.Materials.Opaque_MM": masterMat = resourcePCC.Exports.First(exp => exp.FullPath == "Materials.Opaque_MM_INST"); hasDefaultMaster = false; break; case "BIOG_APL_MASTER_MATERIAL.Placeable_MM": masterMat = resourcePCC.Exports.First(exp => exp.FullPath == "Materials.Placeable_MM_INST"); hasDefaultMaster = false; break; case "BioT_LevelMaster.Materials.Opaque_Standard_MM": masterMat = resourcePCC.Exports.First(exp => exp.FullPath == "Materials.Opaque_Standard_MM_INST"); hasDefaultMaster = false; break; default: textures = ObjectBinary.From <MaterialInstance>(mat).SM3StaticPermutationResource.UniformExpressionTextures; break; } if (!hasDefaultMaster && mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> texParams) { textures = texParams.Select(structProp => new UIndex(structProp.GetProp <ObjectProperty>("ParameterValue")?.Value ?? 0)).ToArray(); } } } else if (preserveMaterialInstances) { continue; } else if (mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> texParams) { textures = texParams.Select(structProp => new UIndex(structProp.GetProp <ObjectProperty>("ParameterValue")?.Value ?? 0)).ToArray(); } else if (mat.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentProp && package.GetEntry(parentProp.Value) is ExportEntry parent && parent.ClassName == "Material") { textures = ObjectBinary.From <Material>(parent).SM3MaterialResource.UniformExpressionTextures; } if (hasDefaultMaster) { EntryImporter.ReplaceExportDataWithAnother(masterMat, mat); int norm = 0; int diff = 0; foreach (UIndex texture in textures) { if (package.GetEntry(texture) is IEntry tex) { if (diff == 0 && tex.ObjectName.Name.Contains("diff", StringComparison.OrdinalIgnoreCase)) { diff = texture; } else if (norm == 0 && tex.ObjectName.Name.Contains("norm", StringComparison.OrdinalIgnoreCase)) { norm = texture; } } } if (diff == 0) { diff = EntryImporter.GetOrAddCrossImportOrPackage("EngineMaterials.DefaultDiffuse", resourcePCC, package).UIndex; } var matBin = ObjectBinary.From <Material>(mat); matBin.SM3MaterialResource.UniformExpressionTextures = new UIndex[] { norm, diff }; mat.WriteBinary(matBin); mat.Class = package.Imports.First(imp => imp.ObjectName == "Material"); } else if (mat.ClassName == "Material") { var mmparent = EntryImporter.GetOrAddCrossImportOrPackage(masterMat.ParentFullPath, resourcePCC, package); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, masterMat, package, mmparent, true, out IEntry targetexp); mat.ReplaceAllReferencesToThisOne(targetexp); EntryPruner.TrashEntryAndDescendants(mat); } else if (mat.ClassName == "MaterialInstanceConstant") { try { var matprops = mat.GetProperties(); var parentlightguid = masterMat.GetProperty <StructProperty>("ParentLightingGuid"); matprops.AddOrReplaceProp(parentlightguid); var mguid = masterMat.GetProperty <StructProperty>("m_Guid"); matprops.AddOrReplaceProp(mguid); var lguid = masterMat.GetProperty <StructProperty>("LightingGuid"); matprops.AddOrReplaceProp(lguid); var masterBin = ObjectBinary.From <MaterialInstance>(masterMat); var matBin = ObjectBinary.From <MaterialInstance>(mat); var staticResTextures3 = masterBin.SM3StaticPermutationResource.UniformExpressionTextures.ToList(); var newtextures3 = new List <UIndex>(); var staticResTextures2 = masterBin.SM2StaticPermutationResource.UniformExpressionTextures.ToList(); var newtextures2 = new List <UIndex>(); IEntry norm = null; IEntry diff = null; IEntry spec = null; foreach (var texref in textures) { IEntry texEnt = package.GetEntry(texref); string texName = texEnt?.ObjectName ?? "None"; if (texName.ToLowerInvariant().Contains("norm")) { norm = texEnt; } else if (texName.ToLowerInvariant().Contains("diff")) { diff = texEnt; } else if (texName.ToLowerInvariant().Contains("spec")) { spec = texEnt; } else if (texName.ToLowerInvariant().Contains("msk")) { spec = texEnt; } } foreach (var texidx in staticResTextures2) { var masterTxt = resourcePCC.GetEntry(texidx); IEntry newTxtEnt = masterTxt; switch (masterTxt?.ObjectName.Name) { case "DefaultDiffuse": if (diff != null) { newTxtEnt = diff; } break; case "DefaultNormal": if (norm != null) { newTxtEnt = norm; } break; case "Gray": //Spec if (spec != null) { newTxtEnt = spec; } break; default: break; } var newtexidx = package.Exports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0; if (newtexidx == 0) { newtexidx = package.Imports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0; } if (newTxtEnt == masterTxt && newtexidx == 0) { var texparent = EntryImporter.GetOrAddCrossImportOrPackage(newTxtEnt.ParentFullPath, resourcePCC, package); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, newTxtEnt, package, texparent, true, out IEntry newtext); newtextures2.Add(newtext?.UIndex ?? 0); } else { newtextures2.Add(newtexidx); } } foreach (var texidx in staticResTextures3) { var masterTxt = resourcePCC.GetEntry(texidx); IEntry newTxtEnt = masterTxt; switch (masterTxt?.ObjectName) { case "DefaultDiffuse": if (diff != null) { newTxtEnt = diff; } break; case "DefaultNormal": if (norm != null) { newTxtEnt = norm; } break; case "Gray": //Spec if (spec != null) { newTxtEnt = spec; } break; default: break; } var newtexidx = package.Exports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0; if (newtexidx == 0) { newtexidx = package.Imports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0; } if (newTxtEnt == masterTxt && newtexidx == 0) { var texparent = EntryImporter.GetOrAddCrossImportOrPackage(newTxtEnt.ParentFullPath, resourcePCC, package); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, newTxtEnt, package, texparent, true, out IEntry newtext); newtextures3.Add(newtext?.UIndex ?? 0); } else { newtextures3.Add(newtexidx); } } masterBin.SM2StaticPermutationResource.UniformExpressionTextures = newtextures2.ToArray(); masterBin.SM3StaticPermutationResource.UniformExpressionTextures = newtextures3.ToArray(); mat.WritePropertiesAndBinary(matprops, masterBin); } catch { Debug.WriteLine("MaterialInstanceConversion error"); } } } } } }
public static string GenerateUDKFileForLevel(string udkPath, IMEPackage pcc) { #region AssetPackage string meshPackageName = $"{Path.GetFileNameWithoutExtension(pcc.FilePath)}Meshes"; string meshFile = Path.Combine(udkPath, @"UDKGame\Content\Shared\", $"{meshPackageName}.upk"); MEPackageHandler.CreateAndSavePackage(meshFile, MEGame.UDK); using IMEPackage meshPackage = MEPackageHandler.OpenUDKPackage(meshFile); meshPackage.getEntryOrAddImport("Core.Package"); IEntry defMat = meshPackage.getEntryOrAddImport("EngineMaterials.DefaultMaterial", "Material", "Engine"); var allMats = new HashSet <int>(); var relinkMap = new Dictionary <IEntry, IEntry>(); #region StaticMeshes List <ExportEntry> staticMeshes = pcc.Exports.Where(exp => exp.ClassName == "StaticMesh").ToList(); foreach (ExportEntry mesh in staticMeshes) { var mats = new Queue <int>(); StaticMesh stm = ObjectBinary.From <StaticMesh>(mesh); foreach (StaticMeshRenderData lodModel in stm.LODModels) { foreach (StaticMeshElement meshElement in lodModel.Elements) { mats.Enqueue(meshElement.Material); allMats.Add(meshElement.Material); meshElement.Material = 0; } } if (pcc.GetEntry(stm.BodySetup) is ExportEntry rbBodySetup) { rbBodySetup.RemoveProperty("PhysMaterial"); } mesh.WriteBinary(stm); IEntry newParent = EntryImporter.GetOrAddCrossImportOrPackage(mesh.ParentFullPath, pcc, meshPackage); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild, mesh, meshPackage, newParent, false, out IEntry ent, relinkMap); ExportEntry portedMesh = (ExportEntry)ent; stm = ObjectBinary.From <StaticMesh>(portedMesh); foreach (StaticMeshRenderData lodModel in stm.LODModels) { foreach (StaticMeshElement meshElement in lodModel.Elements) { meshElement.Material = mats.Dequeue(); } } portedMesh.WriteBinary(stm); } #endregion #region Materials using (IMEPackage udkResources = MEPackageHandler.OpenMEPackageFromStream(Utilities.GetCustomAppResourceStream(MEGame.UDK))) { ExportEntry normDiffMat = udkResources.Exports.First(exp => exp.ObjectName == "NormDiffMat"); foreach (int matUIndex in allMats) { if (pcc.GetEntry(matUIndex) is ExportEntry matExp) { List <IEntry> textures = new MaterialInstanceConstant(matExp).Textures; ExportEntry diff = null; ExportEntry norm = null; foreach (IEntry texEntry in textures) { if (texEntry is ExportEntry texport) { if (texport.ObjectName.Name.ToLower().Contains("diff")) { diff = texport; } else if (texport.ObjectName.Name.ToLower().Contains("norm")) { norm = texport; } } } if (diff == null) { relinkMap[matExp] = defMat; continue; } else { EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.AddSingularAsChild, diff, meshPackage, null, false, out IEntry ent); diff = (ExportEntry)ent; diff.RemoveProperty("TextureFileCacheName"); diff.RemoveProperty("TFCFileGuid"); diff.RemoveProperty("LODGroup"); } if (norm != null) { EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.AddSingularAsChild, norm, meshPackage, null, false, out IEntry ent); norm = (ExportEntry)ent; norm.RemoveProperty("TextureFileCacheName"); norm.RemoveProperty("TFCFileGuid"); norm.RemoveProperty("LODGroup"); } EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild, normDiffMat, meshPackage, null, true, out IEntry matEnt); ExportEntry newMat = (ExportEntry)matEnt; newMat.ObjectName = matExp.ObjectName; Material matBin = ObjectBinary.From <Material>(newMat); matBin.SM3MaterialResource.UniformExpressionTextures = new UIndex[] { norm?.UIndex ?? 0, diff.UIndex }; newMat.WriteBinary(matBin); relinkMap[matExp] = newMat; if (newMat.GetProperty <ArrayProperty <ObjectProperty> >("Expressions") is {} expressionsProp&& expressionsProp.Count >= 2) { ExportEntry diffExpression = meshPackage.GetUExport(expressionsProp[0].Value); ExportEntry normExpression = meshPackage.GetUExport(expressionsProp[1].Value); diffExpression.WriteProperty(new ObjectProperty(diff.UIndex, "Texture")); normExpression.WriteProperty(new ObjectProperty(norm?.UIndex ?? 0, "Texture")); } } else if (pcc.GetEntry(matUIndex) is ImportEntry matImp) { relinkMap[matImp] = defMat; } } var relinkMapping = new OrderedMultiValueDictionary <IEntry, IEntry>(relinkMap); foreach (ExportEntry stmExport in staticMeshes) { if (relinkMap.TryGetValue(stmExport, out IEntry destEnt) && destEnt is ExportEntry destExp) { Relinker.Relink(stmExport, destExp, relinkMapping); } } } #endregion meshPackage.Save(); #endregion var staticMeshActors = new List <ExportEntry>(); var lightActors = new List <ExportEntry>(); string tempPackagePath = Path.Combine(App.ExecFolder, $"{Path.GetFileNameWithoutExtension(pcc.FilePath)}.udk"); File.Copy(Path.Combine(App.ExecFolder, "empty.udk"), tempPackagePath, true); using IMEPackage udkPackage = MEPackageHandler.OpenUDKPackage(tempPackagePath); { var topLevelMeshPackages = new List <IEntry>(); foreach (ExportEntry exportEntry in staticMeshes) { IEntry imp = udkPackage.getEntryOrAddImport($"{exportEntry.FullPath}", "StaticMesh", "Engine", exportEntry.ObjectName.Number); while (imp.Parent != null) { imp = imp.Parent; } if (!topLevelMeshPackages.Contains(imp)) { topLevelMeshPackages.Add(imp); } } ExportEntry levelExport = udkPackage.Exports.First(exp => exp.ClassName == "Level"); List <int> actorsInLevel = ObjectBinary.From <Level>(pcc.Exports.First(exp => exp.ClassName == "Level")).Actors.Select(u => u.value).ToList(); var componentToMatrixMap = new Dictionary <int, Matrix>(); foreach (int uIndex in actorsInLevel) { if (pcc.GetEntry(uIndex) is ExportEntry stcExp) { if (stcExp.ClassName == "StaticMeshCollectionActor") { StaticMeshCollectionActor stmc = ObjectBinary.From <StaticMeshCollectionActor>(stcExp); var components = stcExp.GetProperty <ArrayProperty <ObjectProperty> >("StaticMeshComponents"); for (int i = 0; i < components.Count; i++) { componentToMatrixMap[components[i].Value] = stmc.LocalToWorldTransforms[i]; } } else if (stcExp.ClassName == "StaticLightCollectionActor") { StaticLightCollectionActor stlc = ObjectBinary.From <StaticLightCollectionActor>(stcExp); var components = stcExp.GetProperty <ArrayProperty <ObjectProperty> >("LightComponents"); for (int i = 0; i < components.Count; i++) { componentToMatrixMap[components[i].Value] = stlc.LocalToWorldTransforms[i]; } } } } #region StaticMeshActors { var emptySMCBin = new StaticMeshComponent(); IEntry staticMeshActorClass = udkPackage.getEntryOrAddImport("Engine.StaticMeshActor"); udkPackage.getEntryOrAddImport("Engine.Default__StaticMeshActor", "StaticMeshActor", "Engine"); IEntry staticMeshComponentArchetype = udkPackage.getEntryOrAddImport("Engine.Default__StaticMeshActor.StaticMeshComponent0", "StaticMeshComponent", "Engine"); int smaIndex = 2; int smcIndex = 2; foreach (ExportEntry smc in pcc.Exports.Where(exp => exp.ClassName == "StaticMeshComponent")) { if (smc.Parent is ExportEntry parent && actorsInLevel.Contains(parent.UIndex) && parent.IsA("StaticMeshActorBase")) { StructProperty locationProp; StructProperty rotationProp; StructProperty scaleProp = null; smc.CondenseArchetypes(); if (!(smc.GetProperty <ObjectProperty>("StaticMesh") is { } meshProp) || !pcc.IsUExport(meshProp.Value)) { continue; } smc.WriteBinary(emptySMCBin); smc.RemoveProperty("bBioIsReceivingDecals"); smc.RemoveProperty("bBioForcePrecomputedShadows"); //smc.RemoveProperty("bUsePreComputedShadows"); smc.RemoveProperty("bAcceptsLights"); smc.RemoveProperty("IrrelevantLights"); smc.RemoveProperty("Materials"); //should make use of this? smc.ObjectName = new NameReference("StaticMeshComponent", smcIndex++); if (parent.ClassName == "StaticMeshCollectionActor") { if (!componentToMatrixMap.TryGetValue(smc.UIndex, out Matrix m)) { continue; } (Vector3 posVec, Vector3 scaleVec, Rotator rotator) = m.UnrealDecompose(); locationProp = CommonStructs.Vector3Prop(posVec, "Location"); rotationProp = CommonStructs.RotatorProp(rotator, "Rotation"); scaleProp = CommonStructs.Vector3Prop(scaleVec, "DrawScale3D"); //smc.WriteProperty(CommonStructs.Matrix(m, "CachedParentToWorld")); } else { locationProp = parent.GetProperty <StructProperty>("Location"); rotationProp = parent.GetProperty <StructProperty>("Rotation"); scaleProp = parent.GetProperty <StructProperty>("DrawScale3D"); if (parent.GetProperty <FloatProperty>("DrawScale")?.Value is float scale) { Vector3 scaleVec = Vector3.One; if (scaleProp != null) { scaleVec = CommonStructs.GetVector3(scaleProp); } scaleProp = CommonStructs.Vector3Prop(scaleVec * scale, "DrawScale3D"); } } ExportEntry sma = new ExportEntry(udkPackage, EntryImporter.CreateStack(MEGame.UDK, staticMeshActorClass.UIndex)) { ObjectName = new NameReference("StaticMeshActor", smaIndex++), Class = staticMeshActorClass, Parent = levelExport }; sma.ObjectFlags |= UnrealFlags.EObjectFlags.HasStack; udkPackage.AddExport(sma); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.AddSingularAsChild, smc, udkPackage, sma, true, out IEntry result); var props = new PropertyCollection { new ObjectProperty(result.UIndex, "StaticMeshComponent"), new NameProperty(new NameReference(Path.GetFileNameWithoutExtension(smc.FileRef.FilePath), smc.UIndex), "Tag"), new ObjectProperty(result.UIndex, "CollisionComponent") }; if (locationProp != null) { props.Add(locationProp); } if (rotationProp != null) { props.Add(rotationProp); } if (scaleProp != null) { props.Add(scaleProp); } sma.WriteProperties(props); staticMeshActors.Add(sma); } } IEntry topMeshPackageImport = udkPackage.getEntryOrAddImport(meshPackageName, "Package"); foreach (IEntry mp in topLevelMeshPackages) { mp.Parent = topMeshPackageImport; } } #endregion #region LightActors { IEntry pointLightClass = udkPackage.getEntryOrAddImport("Engine.PointLight"); IEntry spotLightClass = udkPackage.getEntryOrAddImport("Engine.SpotLight"); IEntry directionalLightClass = udkPackage.getEntryOrAddImport("Engine.DirectionalLight"); int plaIndex = 1; int plcIndex = 1; int slaIndex = 1; int slcIndex = 1; int dlaIndex = 1; int dlcIndex = 1; foreach (ExportEntry lightComponent in pcc.Exports) { if (!(lightComponent.Parent is ExportEntry parent && actorsInLevel.Contains(parent.UIndex))) { continue; } StructProperty locationProp; StructProperty rotationProp; StructProperty scaleProp; switch (lightComponent.ClassName) { case "PointLightComponent": lightComponent.CondenseArchetypes(); lightComponent.ObjectName = new NameReference("PointLightComponent", plcIndex++); if (parent.ClassName == "StaticLightCollectionActor") { if (!componentToMatrixMap.TryGetValue(lightComponent.UIndex, out Matrix m)) { continue; } (Vector3 posVec, Vector3 scaleVec, Rotator rotator) = m.UnrealDecompose(); locationProp = CommonStructs.Vector3Prop(posVec, "Location"); rotationProp = CommonStructs.RotatorProp(rotator, "Rotation"); scaleProp = CommonStructs.Vector3Prop(scaleVec, "DrawScale3D"); } else { locationProp = parent.GetProperty <StructProperty>("Location"); rotationProp = parent.GetProperty <StructProperty>("Rotation"); scaleProp = parent.GetProperty <StructProperty>("DrawScale3D"); if (parent.GetProperty <FloatProperty>("DrawScale")?.Value is float scale) { Vector3 scaleVec = Vector3.One; if (scaleProp != null) { scaleVec = CommonStructs.GetVector3(scaleProp); } scaleProp = CommonStructs.Vector3Prop(scaleVec * scale, "DrawScale3D"); } } ExportEntry pla = new ExportEntry(udkPackage, EntryImporter.CreateStack(MEGame.UDK, pointLightClass.UIndex)) { ObjectName = new NameReference("PointLight", plaIndex++), Class = pointLightClass, Parent = levelExport }; pla.ObjectFlags |= UnrealFlags.EObjectFlags.HasStack; udkPackage.AddExport(pla); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.AddSingularAsChild, lightComponent, udkPackage, pla, true, out IEntry portedPLC); var plsProps = new PropertyCollection { new ObjectProperty(portedPLC.UIndex, "LightComponent"), new NameProperty("PointLight", "Tag"), }; if (locationProp != null) { plsProps.Add(locationProp); } if (rotationProp != null) { plsProps.Add(rotationProp); } if (scaleProp != null) { plsProps.Add(scaleProp); } pla.WriteProperties(plsProps); lightActors.Add(pla); break; case "SpotLightComponent": lightComponent.CondenseArchetypes(); lightComponent.ObjectName = new NameReference("SpotLightComponent", slcIndex++); if (parent.ClassName == "StaticLightCollectionActor") { if (!componentToMatrixMap.TryGetValue(lightComponent.UIndex, out Matrix m)) { continue; } (Vector3 posVec, Vector3 scaleVec, Rotator rotator) = m.UnrealDecompose(); locationProp = CommonStructs.Vector3Prop(posVec, "Location"); rotationProp = CommonStructs.RotatorProp(rotator, "Rotation"); scaleProp = CommonStructs.Vector3Prop(scaleVec, "DrawScale3D"); } else { locationProp = parent.GetProperty <StructProperty>("Location"); rotationProp = parent.GetProperty <StructProperty>("Rotation"); scaleProp = parent.GetProperty <StructProperty>("DrawScale3D"); if (parent.GetProperty <FloatProperty>("DrawScale")?.Value is float scale) { Vector3 scaleVec = Vector3.One; if (scaleProp != null) { scaleVec = CommonStructs.GetVector3(scaleProp); } scaleProp = CommonStructs.Vector3Prop(scaleVec * scale, "DrawScale3D"); } } ExportEntry sla = new ExportEntry(udkPackage, EntryImporter.CreateStack(MEGame.UDK, spotLightClass.UIndex)) { ObjectName = new NameReference("SpotLight", slaIndex++), Class = spotLightClass, Parent = levelExport }; sla.ObjectFlags |= UnrealFlags.EObjectFlags.HasStack; udkPackage.AddExport(sla); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.AddSingularAsChild, lightComponent, udkPackage, sla, true, out IEntry portedSLC); var slaProps = new PropertyCollection { new ObjectProperty(portedSLC.UIndex, "LightComponent"), new NameProperty("SpotLight", "Tag"), }; if (locationProp != null) { slaProps.Add(locationProp); } if (rotationProp != null) { slaProps.Add(rotationProp); } if (scaleProp != null) { slaProps.Add(scaleProp); } sla.WriteProperties(slaProps); lightActors.Add(sla); break; case "DirectionalLightComponent": lightComponent.CondenseArchetypes(); lightComponent.ObjectName = new NameReference("DirectionalLightComponent", dlcIndex++); if (parent.ClassName == "StaticLightCollectionActor") { if (!componentToMatrixMap.TryGetValue(lightComponent.UIndex, out Matrix m)) { continue; } (Vector3 posVec, Vector3 scaleVec, Rotator rotator) = m.UnrealDecompose(); locationProp = CommonStructs.Vector3Prop(posVec, "Location"); rotationProp = CommonStructs.RotatorProp(rotator, "Rotation"); scaleProp = CommonStructs.Vector3Prop(scaleVec, "DrawScale3D"); } else { locationProp = parent.GetProperty <StructProperty>("Location"); rotationProp = parent.GetProperty <StructProperty>("Rotation"); scaleProp = parent.GetProperty <StructProperty>("DrawScale3D"); if (parent.GetProperty <FloatProperty>("DrawScale")?.Value is float scale) { Vector3 scaleVec = Vector3.One; if (scaleProp != null) { scaleVec = CommonStructs.GetVector3(scaleProp); } scaleProp = CommonStructs.Vector3Prop(scaleVec * scale, "DrawScale3D"); } } ExportEntry dla = new ExportEntry(udkPackage, EntryImporter.CreateStack(MEGame.UDK, directionalLightClass.UIndex)) { ObjectName = new NameReference("DirectionalLight", dlaIndex++), Class = directionalLightClass, Parent = levelExport }; dla.ObjectFlags |= UnrealFlags.EObjectFlags.HasStack; udkPackage.AddExport(dla); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.AddSingularAsChild, lightComponent, udkPackage, dla, true, out IEntry portedDLC); var dlaProps = new PropertyCollection { new ObjectProperty(portedDLC.UIndex, "LightComponent"), new NameProperty("DirectionalLight", "Tag"), }; if (locationProp != null) { dlaProps.Add(locationProp); } if (rotationProp != null) { dlaProps.Add(rotationProp); } if (scaleProp != null) { dlaProps.Add(scaleProp); } dla.WriteProperties(dlaProps); lightActors.Add(dla); break; } } } UDKifyLights(udkPackage); #endregion Level level = ObjectBinary.From <Level>(levelExport); level.Actors = levelExport.GetChildren().Where(ent => ent.IsA("Actor")).Select(ent => new UIndex(ent.UIndex)).ToList(); levelExport.WriteBinary(level); udkPackage.Save(); } string resultFilePath = Path.Combine(udkPath, @"UDKGame\Content\Maps\", $"{Path.GetFileNameWithoutExtension(pcc.FilePath)}.udk"); using (IMEPackage udkPackage2 = MEPackageHandler.OpenUDKPackage(Path.Combine(App.ExecFolder, "empty.udk"))) { ExportEntry levelExport = udkPackage2.Exports.First(exp => exp.ClassName == "Level"); Level levelBin = ObjectBinary.From <Level>(levelExport); foreach (ExportEntry actor in staticMeshActors) { EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild, actor, udkPackage2, levelExport, true, out IEntry result); levelBin.Actors.Add(result.UIndex); } foreach (ExportEntry actor in lightActors) { EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild, actor, udkPackage2, levelExport, true, out IEntry result); levelBin.Actors.Add(result.UIndex); } levelExport.WriteBinary(levelBin); udkPackage2.Save(resultFilePath); } File.Delete(tempPackagePath); return(resultFilePath); }
public static List <string> Relink(ExportEntry sourceExport, ExportEntry relinkingExport, OrderedMultiValueDictionary <IEntry, IEntry> crossPCCObjectMappingList, bool importExportDependencies = false) { var relinkFailedReport = new List <string>(); IMEPackage sourcePcc = sourceExport.FileRef; //Relink stack if (relinkingExport.HasStack) { byte[] stack = relinkingExport.GetStack(); int uIndex = BitConverter.ToInt32(stack, 0); string relinkResult = relinkUIndex(sourceExport.FileRef, relinkingExport, ref uIndex, "Stack: Node", crossPCCObjectMappingList, "", importExportDependencies); if (relinkResult is null) { stack.OverwriteRange(0, BitConverter.GetBytes(uIndex)); } else { relinkFailedReport.Add(relinkResult); } uIndex = BitConverter.ToInt32(stack, 4); relinkResult = relinkUIndex(sourceExport.FileRef, relinkingExport, ref uIndex, "Stack: StateNode", crossPCCObjectMappingList, "", importExportDependencies); if (relinkResult is null) { stack.OverwriteRange(4, BitConverter.GetBytes(uIndex)); } else { relinkFailedReport.Add(relinkResult); } relinkingExport.SetStack(stack); } //Relink Properties PropertyCollection transplantProps = sourceExport.GetProperties(); relinkFailedReport.AddRange(relinkPropertiesRecursive(sourcePcc, relinkingExport, transplantProps, crossPCCObjectMappingList, "", importExportDependencies)); relinkingExport.WriteProperties(transplantProps); //Relink Binary try { if (relinkingExport.Game != sourcePcc.Game && (relinkingExport.IsClass || relinkingExport.ClassName == "State" || relinkingExport.ClassName == "Function")) { relinkFailedReport.Add($"{relinkingExport.UIndex} {relinkingExport.FullPath} binary relinking failed. Cannot port {relinkingExport.ClassName} between games!"); return(relinkFailedReport); } if (ObjectBinary.From(relinkingExport) is ObjectBinary objBin) { List <(UIndex, string)> indices = objBin.GetUIndexes(relinkingExport.FileRef.Game); foreach ((UIndex uIndex, string propName) in indices) { string result = relinkUIndex(sourcePcc, relinkingExport, ref uIndex.value, $"(Binary Property: {propName})", crossPCCObjectMappingList, "", importExportDependencies); if (result != null) { relinkFailedReport.Add(result); } } //UStruct is abstract baseclass for Class, State, and Function, and can have script in it if (objBin is UStruct uStructBinary && uStructBinary.ScriptBytes.Length > 0) { if (relinkingExport.Game == MEGame.ME3) { (List <Token> tokens, _) = Bytecode.ParseBytecode(uStructBinary.ScriptBytes, sourceExport); foreach (Token token in tokens) { relinkFailedReport.AddRange(RelinkToken(token, uStructBinary.ScriptBytes, sourceExport, relinkingExport, crossPCCObjectMappingList, importExportDependencies)); } } else { relinkFailedReport.Add($"{relinkingExport.UIndex} {relinkingExport.FullPath} binary relinking failed. {relinkingExport.ClassName} contains script, " + $"which cannot be relinked for {relinkingExport.Game}"); } } relinkingExport.setBinaryData(objBin.ToBytes(relinkingExport.FileRef, relinkingExport.DataOffset + relinkingExport.propsEnd())); return(relinkFailedReport); } byte[] binarydata = relinkingExport.getBinaryData(); if (binarydata.Length > 0) { switch (relinkingExport.ClassName) { //todo: make a WwiseEvent ObjectBinary class case "WwiseEvent": { void relinkAtPosition(int binaryPosition, string propertyName) { int uIndex = BitConverter.ToInt32(binarydata, binaryPosition); string relinkResult = relinkUIndex(sourcePcc, relinkingExport, ref uIndex, propertyName, crossPCCObjectMappingList, "", importExportDependencies); if (relinkResult is null) { binarydata.OverwriteRange(binaryPosition, BitConverter.GetBytes(uIndex)); } else { relinkFailedReport.Add(relinkResult); } } if (relinkingExport.FileRef.Game == MEGame.ME3) { int count = BitConverter.ToInt32(binarydata, 0); for (int j = 0; j < count; j++) { relinkAtPosition(4 + (j * 4), $"(Binary Property: WwiseStreams[{j}])"); } relinkingExport.setBinaryData(binarydata); } else if (relinkingExport.FileRef.Game == MEGame.ME2) { int parsingPos = 4; int linkCount = BitConverter.ToInt32(binarydata, parsingPos); parsingPos += 4; for (int j = 0; j < linkCount; j++) { int bankcount = BitConverter.ToInt32(binarydata, parsingPos); parsingPos += 4; for (int k = 0; k < bankcount; k++) { relinkAtPosition(parsingPos, $"(Binary Property: link[{j}].WwiseBanks[{k}])"); parsingPos += 4; } int wwisestreamcount = BitConverter.ToInt32(binarydata, parsingPos); parsingPos += 4; for (int k = 0; k < wwisestreamcount; k++) { relinkAtPosition(parsingPos, $"(Binary Property: link[{j}].WwiseStreams[{k}])"); parsingPos += 4; } } relinkingExport.setBinaryData(binarydata); } } break; case "DominantDirectionalLightComponent": case "SphericalHarmonicLightComponent": case "DominantPointLightComponent": case "StaticLightCollectionActor": case "DominantSpotLightComponent": case "DirectionalLightComponent": case "StaticMeshCollectionActor": case "TerrainWeightMapTexture": case "PhysicsAssetInstance": case "PointLightComponent": case "ShadowMapTexture2D": case "SpotLightComponent": case "LightMapTexture2D": case "SkyLightComponent": case "TextureFlipBook": case "BrushComponent": case "FaceFXAnimSet": case "TextureMovie": case "AnimSequence": case "RB_BodySetup": case "MorphTarget": case "ShadowMap1D": case "WwiseStream": case "WwiseBank": case "Texture2D": //these classes have binary but do not need relinking break; default: if (binarydata.Any(b => b != 0)) { relinkFailedReport.Add($"{relinkingExport.UIndex} {relinkingExport.FullPath} has unparsed binary. " + $"This binary may contain items that need to be relinked. Come to the Discord server " + $"(click ME3Tweaks logo in main window for invite) and ask devs to parse this class."); } break; } } } catch (Exception e) when(!App.IsDebug) { relinkFailedReport.Add($"{relinkingExport.UIndex} {relinkingExport.FullPath} binary relinking failed due to exception: {e.Message}"); } return(relinkFailedReport); }
public static void ScanStuff(PackageEditorWPF pewpf) { //var filePaths = MELoadedFiles.GetOfficialFiles(MEGame.ME3);//.Concat(MELoadedFiles.GetOfficialFiles(MEGame.ME2));//.Concat(MELoadedFiles.GetOfficialFiles(MEGame.ME1)); //var filePaths = MELoadedFiles.GetAllFiles(game); /*"Core.pcc", "Engine.pcc", "GameFramework.pcc", "GFxUI.pcc", "WwiseAudio.pcc", "SFXOnlineFoundation.pcc", "SFXGame.pcc" */ var filePaths = new[] { "Core.pcc", "Engine.pcc", "GameFramework.pcc", "GFxUI.pcc", "WwiseAudio.pcc", "SFXOnlineFoundation.pcc" }.Select(f => Path.Combine(ME3Directory.CookedPCPath, f)); var interestingExports = new List <EntryStringPair>(); var foundClasses = new HashSet <string>(); //new HashSet<string>(BinaryInterpreterWPF.ParsableBinaryClasses); var foundProps = new Dictionary <string, string>(); var unkOpcodes = new List <int>();//Enumerable.Range(0x5B, 8).ToList(); unkOpcodes.Add(0); unkOpcodes.Add(1); var unkOpcodesInfo = unkOpcodes.ToDictionary(i => i, i => new OpcodeInfo()); var comparisonDict = new Dictionary <string, (byte[] original, byte[] newData)>(); var extraInfo = new HashSet <string>(); pewpf.IsBusy = true; pewpf.BusyText = "Scanning"; Task.Run(() => { //preload base files for faster scanning using var baseFiles = MEPackageHandler.OpenMEPackages(EntryImporter.FilesSafeToImportFrom(MEGame.ME3) .Select(f => Path.Combine(ME3Directory.CookedPCPath, f))); baseFiles.Add( MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "BIOP_MP_COMMON.pcc"))); foreach (string filePath in filePaths) { //ScanShaderCache(filePath); //ScanMaterials(filePath); //ScanStaticMeshComponents(filePath); //ScanLightComponents(filePath); //ScanLevel(filePath); //if (findClass(filePath, "ShaderCache", true)) break; //findClassesWithBinary(filePath); ScanScripts2(filePath); //if (interestingExports.Count > 0) //{ // break; //} //if (resolveImports(filePath)) break; } }).ContinueWithOnUIThread(prevTask => { pewpf.IsBusy = false; interestingExports.Add(new EntryStringPair(null, string.Join("\n", extraInfo))); var listDlg = new ListDialog(interestingExports, "Interesting Exports", "", pewpf) { DoubleClickEntryHandler = entryItem => { if (entryItem?.Entry is IEntry entryToSelect) { PackageEditorWPF p = new PackageEditorWPF(); p.Show(); p.LoadFile(entryToSelect.FileRef.FilePath, entryToSelect.UIndex); p.Activate(); if (comparisonDict.TryGetValue($"{entryToSelect.UIndex} {entryToSelect.FileRef.FilePath}", out (byte[] original, byte[] newData)val)) { File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "original.bin"), val.original); File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "new.bin"), val.newData); } } } }; listDlg.Show(); }); #region extra scanning functions bool findClass(string filePath, string className, bool withBinary = false) { Debug.WriteLine($" {filePath}"); using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath)) { //if (!pcc.IsCompressed) return false; var exports = pcc.Exports.Where(exp => !exp.IsDefaultObject && exp.IsA(className)); foreach (ExportEntry exp in exports) { try { //Debug.WriteLine($"{exp.UIndex}: {filePath}"); var originalData = exp.Data; exp.WriteBinary(ObjectBinary.From(exp)); var newData = exp.Data; if (!originalData.SequenceEqual(newData)) { interestingExports.Add(exp); File.WriteAllBytes( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "original.bin"), originalData); File.WriteAllBytes( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "new.bin"), newData); return(true); } } catch (Exception exception) { Console.WriteLine(exception); interestingExports.Add(new EntryStringPair(exp, $"{exception}")); return(true); } } } return(false); } void findClassesWithBinary(string filePath) { using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath)) { foreach (ExportEntry exp in pcc.Exports.Where(exp => !exp.IsDefaultObject)) { try { if (!foundClasses.Contains(exp.ClassName) && exp.propsEnd() < exp.DataSize) { if (ObjectBinary.From(exp) != null) { foundClasses.Add(exp.ClassName); } else if (exp.GetBinaryData().Any(b => b != 0)) { foundClasses.Add(exp.ClassName); interestingExports.Add(exp); } } } catch (Exception exception) { Console.WriteLine(exception); interestingExports.Add(new EntryStringPair(exp, $"{exp.UIndex}: {filePath}\n{exception}")); } } } } void ScanShaderCache(string filePath) { using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath)) { ExportEntry shaderCache = pcc.Exports.FirstOrDefault(exp => exp.ClassName == "ShaderCache"); if (shaderCache == null) { return; } int oldDataOffset = shaderCache.DataOffset; try { MemoryStream binData = new MemoryStream(shaderCache.Data); binData.JumpTo(shaderCache.propsEnd() + 1); int nameList1Count = binData.ReadInt32(); binData.Skip(nameList1Count * 12); int namelist2Count = binData.ReadInt32(); //namelist2 binData.Skip(namelist2Count * 12); int shaderCount = binData.ReadInt32(); for (int i = 0; i < shaderCount; i++) { binData.Skip(24); int nextShaderOffset = binData.ReadInt32() - oldDataOffset; binData.Skip(14); if (binData.ReadInt32() != 1111577667) //CTAB { interestingExports.Add(new EntryStringPair(null, $"{binData.Position - 4}: {filePath}")); return; } binData.JumpTo(nextShaderOffset); } int vertexFactoryMapCount = binData.ReadInt32(); binData.Skip(vertexFactoryMapCount * 12); int materialShaderMapCount = binData.ReadInt32(); for (int i = 0; i < materialShaderMapCount; i++) { binData.Skip(16); int switchParamCount = binData.ReadInt32(); binData.Skip(switchParamCount * 32); int componentMaskParamCount = binData.ReadInt32(); //if (componentMaskParamCount != 0) //{ // interestingExports.Add($"{i}: {filePath}"); // return; //} binData.Skip(componentMaskParamCount * 44); int normalParams = binData.ReadInt32(); if (normalParams != 0) { interestingExports.Add(new EntryStringPair(null, $"{i}: {filePath}")); return; } binData.Skip(normalParams * 29); int unrealVersion = binData.ReadInt32(); int licenseeVersion = binData.ReadInt32(); if (unrealVersion != 684 || licenseeVersion != 194) { interestingExports.Add(new EntryStringPair(null, $"{binData.Position - 8}: {filePath}")); return; } int nextMaterialShaderMapOffset = binData.ReadInt32() - oldDataOffset; binData.JumpTo(nextMaterialShaderMapOffset); } } catch (Exception exception) { Console.WriteLine(exception); interestingExports.Add(new EntryStringPair(null, $"{filePath}\n{exception}")); } } } void ScanScripts(string filePath) { using IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath); foreach (ExportEntry exp in pcc.Exports.Where(exp => !exp.IsDefaultObject)) { try { if ((exp.ClassName == "State" || exp.ClassName == "Function") && ObjectBinary.From(exp) is UStruct uStruct) { byte[] data = exp.Data; (_, List <BytecodeSingularToken> tokens) = Bytecode.ParseBytecode(uStruct.ScriptBytes, exp); foreach (var token in tokens) { if (token.CurrentStack.Contains("UNKNOWN") || token.OpCodeString.Contains("UNKNOWN")) { interestingExports.Add(exp); } if (unkOpcodes.Contains(token.OpCode)) { int refUIndex = EndianReader.ToInt32(data, token.StartPos + 1, pcc.Endian); IEntry entry = pcc.GetEntry(refUIndex); if (entry != null && (entry.ClassName == "ByteProperty")) { var info = unkOpcodesInfo[token.OpCode]; info.Usages.Add(pcc.FilePath, exp.UIndex, token.StartPos); info.PropTypes.Add(refUIndex switch { 0 => "Null", _ when entry != null => entry.ClassName, _ => "Invalid" }); if (entry != null) { if (entry.Parent == exp) { info.PropLocations.Add("Local"); } else if (entry.Parent == (exp.Parent.ClassName == "State" ? exp.Parent.Parent : exp.Parent)) { info.PropLocations.Add("ThisClass"); } else if (entry.Parent.ClassName == "Function") { info.PropLocations.Add("OtherFunction"); } else if (exp.Parent.IsA(entry.Parent.ObjectName)) { info.PropLocations.Add("AncestorClass"); } else { info.PropLocations.Add("OtherClass"); } } } } }