public static bool InstallOHKO(RandomizationOption arg) { MERLog.Information("Installing One-Hit KO"); var sfxgame = MERFileSystem.GetPackageFile("SFXGame.pcc"); if (sfxgame != null && File.Exists(sfxgame)) { var sfxgameP = MEPackageHandler.OpenMEPackage(sfxgame); // Blood on screen VFX sfxgameP.GetUExport(29336).RemoveProperty("BleedOutEventPair"); sfxgameP.GetUExport(29336).RemoveProperty("BleedOutVFXTemplate"); // Prevent weird landing glitch var fallingStateLanded = sfxgameP.GetUExport(11293); var landedData = fallingStateLanded.Data; NopRange(landedData, 0x2C, 0x14); fallingStateLanded.Data = landedData; MERFileSystem.SavePackage(sfxgameP); } else { } // ProCer tutorials setting min1Health SetupProCer(); // Player classes - Remove shields, set maxhealth to 1 string[] classes = new[] { "Adept", "Engineer", "Infiltrator", "Sentinel", "Soldier", "Vanguard" }; foreach (var c in classes) { var charClass = MERFileSystem.GetPackageFile($"SFXCharacterClass_{c}.pcc"); if (charClass != null && File.Exists(charClass)) { var charClassP = MEPackageHandler.OpenMEPackage(charClass); var ccLoadout = charClassP.FindExport($"BioChar_Loadouts.Player.PLY_{c}"); var ccProps = ccLoadout.GetProperties(); ccProps.GetProp <ArrayProperty <StructProperty> >("ShieldLoadouts").Clear(); // Remove shields // Set health to 1 var health = ccProps.GetProp <StructProperty>("MaxHealth"); health.GetProp <FloatProperty>("X").Value = 1; health.GetProp <FloatProperty>("Y").Value = 1; ccLoadout.WriteProperties(ccProps); // Remove any passive powers ZeroOutStatList(charClassP.FindExport($"SFXGameContent_Powers.SFXPower_{c}Passive"), "HealthBonus", false); ZeroOutStatList(charClassP.FindExport($"SFXGameContent_Powers.SFXPower_{c}Passive_Evolved1"), "HealthBonus", false); ZeroOutStatList(charClassP.FindExport($"SFXGameContent_Powers.SFXPower_{c}Passive_Evolved2"), "HealthBonus", false); if (c == "Vanguard") { // Patch the immunity effect var shieldEffectOnApplied = charClassP.GetUExport(530); var seData = shieldEffectOnApplied.Data; NopRange(seData, 0x2BF, 0x13); shieldEffectOnApplied.Data = seData; } MERFileSystem.SavePackage(charClassP); } } // Zero out stats in tables MERPackageCache cache = new MERPackageCache(); foreach (var asset in ZeroOutStatAssets) { var statClass = asset.GetAsset(cache); if (statClass != null) { foreach (var zos in asset.PropertiesToZeroOut) { ZeroOutStatList(statClass, zos, true); } } } foreach (var package in cache.GetPackages()) { MERFileSystem.SavePackage(package); } { //var reaveF = MERFileSystem.GetPackageFile("SFXPower_Reave_Player.pcc"); //if (reaveF != null && File.Exists(reaveF)) //{ // var reaveP = MEPackageHandler.OpenMEPackage(reaveF); // var defaultReave = reaveP.GetUExport(27); // var regen = defaultReave.GetProperty<ArrayProperty<FloatProperty>>("HealthRegenMult"); // regen[0].Value = 0; // regen[1].Value = 0; // regen[2].Value = 0; // regen[3].Value = 0; // defaultReave.WriteProperty(regen); // var bonusDuration = defaultReave.GetProperty<ArrayProperty<FloatProperty>>("HealthBonusDuration"); // bonusDuration[0].Value = 0; // bonusDuration[1].Value = 0; // bonusDuration[2].Value = 0; // bonusDuration[3].Value = 0; // defaultReave.WriteProperty(bonusDuration); // MERFileSystem.SavePackage(reaveP); //} } return(true); }
private static void RandomizeTheLongWalk(RandomizationOption option) { var prelongwalkfile = MERFileSystem.GetPackageFile("BioD_EndGm2_200Factory.pcc"); if (prelongwalkfile != null) { // Pre-long walk selection var package = MEPackageHandler.OpenMEPackage(prelongwalkfile); var bioticTeamSeq = package.GetUExport(8609); var activated = package.GetUExport(8484); KismetHelper.RemoveAllLinks(activated); // install new logic var randSwitch = MERSeqTools.InstallRandomSwitchIntoSequence(bioticTeamSeq, 13); // don't include theif or veteran as dlc might not be installed KismetHelper.CreateOutputLink(activated, "Out", randSwitch); // Outputs of random choice KismetHelper.CreateOutputLink(randSwitch, "Link 1", package.GetUExport(1420)); //thane KismetHelper.CreateOutputLink(randSwitch, "Link 2", package.GetUExport(1419)); //jack KismetHelper.CreateOutputLink(randSwitch, "Link 3", package.GetUExport(1403)); //garrus KismetHelper.CreateOutputLink(randSwitch, "Link 4", package.GetUExport(1399)); //legion KismetHelper.CreateOutputLink(randSwitch, "Link 5", package.GetUExport(1417)); //grunt KismetHelper.CreateOutputLink(randSwitch, "Link 6", package.GetUExport(1395)); //jacob KismetHelper.CreateOutputLink(randSwitch, "Link 7", package.GetUExport(1418)); //samara KismetHelper.CreateOutputLink(randSwitch, "Link 8", package.GetUExport(1415)); //mordin KismetHelper.CreateOutputLink(randSwitch, "Link 9", package.GetUExport(1405)); //tali KismetHelper.CreateOutputLink(randSwitch, "Link 10", package.GetUExport(1401)); //morinth KismetHelper.CreateOutputLink(randSwitch, "Link 11", package.GetUExport(1402)); //miranda // kasumi if (MERFileSystem.GetPackageFile("BioH_Thief_00.pcc") != null) { KismetHelper.CreateOutputLink(randSwitch, "Link 12", package.GetUExport(1396)); //kasumi } // zaeed if (MERFileSystem.GetPackageFile("BioH_Veteran_00.pcc") != null) { KismetHelper.CreateOutputLink(randSwitch, "Link 13", package.GetUExport(1416)); //zaeed } MERFileSystem.SavePackage(package); } var biodEndGm2F = MERFileSystem.GetPackageFile("BioD_EndGm2.pcc"); if (biodEndGm2F != null) { var package = MEPackageHandler.OpenMEPackage(biodEndGm2F); var ts = package.GetUExport(7); var ss = ts.GetProperty <ArrayProperty <StructProperty> >("StreamingStates"); // Make walk4 remain loaded while walk5 is active as enemeis may not yet be cleared out var conclusion = ss[8]; var visibleNames = conclusion.GetProp <ArrayProperty <NameProperty> >("VisibleChunkNames"); if (!visibleNames.Any(x => x.Value == "BioD_EndGm2_300Walk04")) { // This has pawns as part of the level so we must make sure it doesn't disappear or player will just see enemies disappear visibleNames.Add(new NameProperty("BioD_EndGm2_300Walk04")); } ts.WriteProperty(ss); MERFileSystem.SavePackage(package); } var longwalkfile = MERFileSystem.GetPackageFile("BioD_EndGm2_300LongWalk.pcc"); if (longwalkfile != null) { // automate TLW var package = MEPackageHandler.OpenMEPackage(longwalkfile); var seq = package.GetUExport(1629); var stopWalking = package.GetUExport(1569); // The auto walk delay on Stop Walking var delay = package.GetUExport(806).Clone(); package.AddExport(delay); delay.WriteProperty(new FloatProperty(ThreadSafeRandom.NextFloat(2, 7), "Duration")); // how long to wait until auto walk KismetHelper.AddObjectToSequence(delay, seq, true); KismetHelper.CreateOutputLink(delay, "Finished", package.GetUExport(156)); KismetHelper.CreateOutputLink(stopWalking, "Out", delay); // Do not allow targeting the escort package.GetUExport(1915).WriteProperty(new IntProperty(0, "bValue")); // stopped walking package.GetUExport(1909).WriteProperty(new IntProperty(0, "bValue")); // loading from save - we will auto start KismetHelper.CreateOutputLink(package.GetUExport(1232), "Out", delay); // post loaded from save init // Do not enable autosaves, cause it makes it easy to cheese this area. Bypass the 'savegame' item KismetHelper.RemoveOutputLinks(package.GetUExport(156)); KismetHelper.CreateOutputLink(package.GetUExport(156), "Out", package.GetUExport(1106)); // Pick a random henchman to go on a date with //var determineEscortLog = package.GetUExport(1118); //var spawnSeq = package.GetUExport(1598); //// disconnect old logic //KismetHelper.RemoveAllLinks(determineEscortLog); // install new logic /*var randSwitch = SeqTools.InstallRandomSwitchIntoSequence(spawnSeq, 12); // don't include theif or veteran as dlc might not be installed * KismetHelper.CreateOutputLink(determineEscortLog, "Out", randSwitch); * * * // Outputs of random choice * * * * KismetHelper.CreateOutputLink(randSwitch, "Link 1", package.GetUExport(1599)); //thane * KismetHelper.CreateOutputLink(randSwitch, "Link 2", package.GetUExport(1601)); //jack * KismetHelper.CreateOutputLink(randSwitch, "Link 3", package.GetUExport(1603)); //garrus * KismetHelper.CreateOutputLink(randSwitch, "Link 4", package.GetUExport(1605)); //legion * KismetHelper.CreateOutputLink(randSwitch, "Link 5", package.GetUExport(1607)); //grunt * KismetHelper.CreateOutputLink(randSwitch, "Link 6", package.GetUExport(1609)); //jacob * KismetHelper.CreateOutputLink(randSwitch, "Link 7", package.GetUExport(1611)); //samara * KismetHelper.CreateOutputLink(randSwitch, "Link 8", package.GetUExport(1613)); //mordin * KismetHelper.CreateOutputLink(randSwitch, "Link 9", package.GetUExport(1615)); //tali * KismetHelper.CreateOutputLink(randSwitch, "Link 10", package.GetUExport(1619)); //morinth * KismetHelper.CreateOutputLink(randSwitch, "Link 11", package.GetUExport(1624)); //miranda */ MERFileSystem.SavePackage(package); } //randomize long walk lengths. var endwalkexportmap = new Dictionary <string, int>() { { "BioD_EndGm2_300Walk01", 40 }, { "BioD_EndGm2_300Walk02", 5344 }, { "BioD_EndGm2_300Walk03", 8884 }, { "BioD_EndGm2_300Walk04", 6370 }, { "BioD_EndGm2_300Walk05", 3190 } }; foreach (var map in endwalkexportmap) { var file = MERFileSystem.GetPackageFile(map.Key + ".pcc"); if (file != null) { var package = MEPackageHandler.OpenMEPackage(file); var export = package.GetUExport(map.Value); export.WriteProperty(new FloatProperty(ThreadSafeRandom.NextFloat(.5, 2.5), "PlayRate")); MERFileSystem.SavePackage(package); } } /*foreach (var f in files) * { * var package = MEPackageHandler.OpenMEPackage(f); * var animExports = package.Exports.Where(x => x.ClassName == "InterpTrackAnimControl"); * foreach (var anim in animExports) * { * var animseqs = anim.GetProperty<ArrayProperty<StructProperty>>("AnimSeqs"); * if (animseqs != null) * { * foreach (var animseq in animseqs) * { * var seqname = animseq.GetProp<NameProperty>("AnimSeqName").Value.Name; * if (seqname.StartsWith("Walk_")) * { * var playrate = animseq.GetProp<FloatProperty>("AnimPlayRate"); * var oldrate = playrate.Value; * if (oldrate != 1) Debugger.Break(); * playrate.Value = ThreadSafeRandom.NextFloat(.2, 6); * var data = anim.Parent.Parent as ExportEntry; * var len = data.GetProperty<FloatProperty>("InterpLength"); * len.Value = len.Value * playrate; //this might need to be changed if its not 1 * data.WriteProperty(len); * } * } * } * anim.WriteProperty(animseqs); * } * SavePackage(package); * }*/ }
// Todo: ME3Exp 5.1: Get rid of this and use the import resolver. It must support a cache so we don't constnatly open packages internal static ExportEntry FindExternalAsset(ImportEntry entry, List <ExportEntry> alreadyLoadedPackageEntries, List <IMEPackage> openedPackages) { //Debug.WriteLine("Finding external asset " + entry.GetFullPath); if (entry.Game == MEGame.ME1) { var sourcePackageInternalPath = entry.FullPath.Substring(entry.FullPath.IndexOf('.') + 1); string baseName = entry.FileRef.FollowLink(entry.idxLink).Split('.')[0].ToUpper() + ".upk"; //Get package filename var preloadedPackageEntry = alreadyLoadedPackageEntries?.FirstOrDefault(x => Path.GetFileName(x.FileRef.FilePath).Equals(baseName, StringComparison.InvariantCultureIgnoreCase)); if (preloadedPackageEntry == null && MELoadedFiles.GetFilesLoadedInGame(MEGame.ME1).TryGetValue(baseName, out string packagePath)) { var package = MEPackageHandler.OpenMEPackage(packagePath); if (openedPackages != null && !openedPackages.Contains(package)) { openedPackages.Add(package); } var foundExp = package.Exports.FirstOrDefault(exp => exp.FullPath == sourcePackageInternalPath && exp.ClassName == entry.ClassName); if (foundExp != null) { return(foundExp); } if (openedPackages == null) { package.Dispose(); } } else { Debug.WriteLine("ME1 External Asset lookup: Using existing preloaded export package"); var foundExp = preloadedPackageEntry.FileRef.Exports.FirstOrDefault(exp => exp.FullPath == sourcePackageInternalPath && exp.ClassName == entry.ClassName); if (foundExp != null) { return(foundExp); } } } else { // Next, split the filename by underscores string filenameWithoutExtension = Path.GetFileNameWithoutExtension(entry.FileRef.FilePath).ToLower(); string containingDirectory = Path.GetDirectoryName(entry.FileRef.FilePath); var packagesToCheck = new List <string>(); var gameFiles = MELoadedFiles.GetFilesLoadedInGame(entry.Game); if (filenameWithoutExtension.StartsWith("bioa_") || filenameWithoutExtension.StartsWith("biod_")) { string[] parts = filenameWithoutExtension.Split('_'); if (parts.Length >= 2) //BioA_Nor_WowThatsAlot310.pcc { string bioad = $"{parts[0]}_{parts[1]}.pcc"; string filename = Path.Combine(containingDirectory, bioad); //BioA_Nor.pcc if (File.Exists(filename)) { packagesToCheck.Add(filename); } else { if (gameFiles.TryGetValue(filename, out string inGamePath)) { packagesToCheck.Add(inGamePath); } } string biop = $"BioP_{parts[1]}.pcc"; filename = Path.Combine(containingDirectory, biop); //BioP_Nor.pcc if (File.Exists(filename)) { packagesToCheck.Add(filename); } else { if (gameFiles.TryGetValue(filename, out string inGamePath)) { packagesToCheck.Add(inGamePath); } } } } // Add globals packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), "SFXGame.pcc")); packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), "EntryMenu.pcc")); packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), entry.Game == MEGame.ME3 ? "Startup.pcc" : "Startup_INT.pcc")); packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), "Engine.pcc")); packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), "Engine.u")); //ME1 foreach (string packagePath in packagesToCheck) { if (File.Exists(packagePath)) { var preloadedPackageEntry = alreadyLoadedPackageEntries?.FirstOrDefault(x => Path.GetFileName(x.FileRef.FilePath).Equals(packagePath, StringComparison.InvariantCultureIgnoreCase)); if (preloadedPackageEntry == null) { var sentry = searchPackageForEntry(packagePath, entry.FullPath, entry.ClassName, openedPackages); if (sentry != null) { return(sentry); } } else { Debug.WriteLine("ME2/3 External Asset lookup: Using existing preloaded export package"); var foundExp = preloadedPackageEntry.FileRef.Exports.FirstOrDefault(exp => exp.FullPath == entry.FullPath && exp.ClassName == entry.ClassName); if (foundExp != null) { return(foundExp); } } } } } Debug.WriteLine("Could not find external asset: " + entry.FullPath); return(null); }
private void CheckModForAFCCompactability(DeploymentChecklistItem item) { bool hasError = false; item.HasError = false; item.ItemText = M3L.GetString(M3L.string_checkingAudioReferencesInMod); var referencedFiles = ModBeingDeployed.GetAllRelativeReferences().Select(x => Path.Combine(ModBeingDeployed.ModPath, x)).ToList(); int numChecked = 0; GameTarget validationTarget = mainWindow.InstallationTargets.FirstOrDefault(x => x.Game == ModBeingDeployed.Game); List <string> gameFiles = MEDirectories.EnumerateGameFiles(validationTarget.Game, validationTarget.TargetPath); var errors = new List <string>(); Dictionary <string, MemoryStream> cachedAudio = new Dictionary <string, MemoryStream>(); foreach (var f in referencedFiles) { if (_closed) { return; } numChecked++; item.ItemText = $@"{M3L.GetString(M3L.string_checkingAudioReferencesInMod)} [{numChecked}/{referencedFiles.Count}]"; if (f.RepresentsPackageFilePath()) { var package = MEPackageHandler.OpenMEPackage(f); var wwiseStreams = package.Exports.Where(x => x.ClassName == @"WwiseStream" && !x.IsDefaultObject).ToList(); foreach (var wwisestream in wwiseStreams) { if (_closed) { return; } //Check each reference. var afcNameProp = wwisestream.GetProperty <NameProperty>(@"Filename"); if (afcNameProp != null) { string afcNameWithExtension = afcNameProp + @".afc"; int audioSize = BitConverter.ToInt32(wwisestream.Data, wwisestream.Data.Length - 8); int audioOffset = BitConverter.ToInt32(wwisestream.Data, wwisestream.Data.Length - 4); string afcPath = null; Stream audioStream = null; var localDirectoryAFCPath = Path.Combine(Path.GetDirectoryName(wwisestream.FileRef.FilePath), afcNameWithExtension); bool isInOfficialArea = false; if (File.Exists(localDirectoryAFCPath)) { //local afc afcPath = localDirectoryAFCPath; } else { //Check game var fullPath = gameFiles.FirstOrDefault(x => Path.GetFileName(x).Equals(afcNameWithExtension, StringComparison.InvariantCultureIgnoreCase)); if (fullPath != null) { afcPath = fullPath; isInOfficialArea = MEDirectories.IsInBasegame(afcPath, validationTarget) || MEDirectories.IsInOfficialDLC(afcPath, validationTarget); } else if (cachedAudio.TryGetValue(afcNameProp.Value.Name, out var cachedAudioStream)) { audioStream = cachedAudioStream; //isInOfficialArea = true; //cached from vanilla SFAR } else if (MEDirectories.OfficialDLC(validationTarget.Game).Any(x => afcNameProp.Value.Name.StartsWith(x))) { var dlcName = afcNameProp.Value.Name.Substring(0, afcNameProp.Value.Name.LastIndexOf(@"_", StringComparison.InvariantCultureIgnoreCase)); var audio = VanillaDatabaseService.FetchFileFromVanillaSFAR(validationTarget, dlcName, afcNameWithExtension); if (audio != null) { cachedAudio[afcNameProp.Value.Name] = audio; } audioStream = audio; //isInOfficialArea = true; as this is in a vanilla SFAR we don't test against this since it will be correct. continue; } else { hasError = true; item.Icon = FontAwesomeIcon.TimesCircle; item.Foreground = Brushes.Red; item.Spinning = false; errors.Add(M3L.GetString(M3L.string_interp_couldNotFindReferencedAFC, wwisestream.FileRef.FilePath, wwisestream.GetInstancedFullPath, afcNameProp.ToString())); continue; } } if (afcPath != null) { audioStream = new FileStream(afcPath, FileMode.Open); } try { audioStream.Seek(audioOffset, SeekOrigin.Begin); if (audioStream.ReadStringASCIINull(4) != @"RIFF") { hasError = true; item.Icon = FontAwesomeIcon.TimesCircle; item.Foreground = Brushes.Red; item.Spinning = false; errors.Add(M3L.GetString(M3L.string_interp_invalidAudioPointer, wwisestream.FileRef.FilePath, wwisestream.GetInstancedFullPath)); if (audioStream is FileStream) { audioStream.Close(); } continue; } //attempt to seek audio length. audioStream.Seek(audioSize + 4, SeekOrigin.Current); //Check if this file is in basegame if (isInOfficialArea) { //Verify offset is not greater than vanilla size var vanillaInfo = VanillaDatabaseService.GetVanillaFileInfo(validationTarget, afcPath.Substring(validationTarget.TargetPath.Length + 1)); if (vanillaInfo == null) { Crashes.TrackError(new Exception($@"Vanilla information was null when performing vanilla file check for {afcPath.Substring(validationTarget.TargetPath.Length + 1)}")); } if (audioOffset >= vanillaInfo[0].size) { hasError = true; item.Icon = FontAwesomeIcon.TimesCircle; item.Foreground = Brushes.Red; item.Spinning = false; errors.Add(M3L.GetString(M3L.string_interp_audioStoredInOfficialAFC, wwisestream.FileRef.FilePath, wwisestream.GetInstancedFullPath)); } } if (audioStream is FileStream) { audioStream.Close(); } } catch (Exception e) { hasError = true; item.Icon = FontAwesomeIcon.TimesCircle; item.Foreground = Brushes.Red; item.Spinning = false; if (audioStream is FileStream) { audioStream.Close(); } errors.Add(M3L.GetString(M3L.string_errorValidatingAudioReference, wwisestream.FileRef.FilePath, wwisestream.GetInstancedFullPath, e.Message)); continue; } } } } } if (!hasError) { item.Foreground = Brushes.Green; item.Icon = FontAwesomeIcon.CheckCircle; item.ItemText = M3L.GetString(M3L.string_noAudioIssuesWereDetected); item.ToolTip = M3L.GetString(M3L.string_validationOK); } else { item.Errors = errors; item.ItemText = M3L.GetString(M3L.string_audioIssuesWereDetected); item.ToolTip = M3L.GetString(M3L.string_validationFailed); } item.HasError = hasError; cachedAudio.Clear(); }
public void LoadMEPackage(string s) { Pcc?.Release(winForm: this); Pcc = MEPackageHandler.OpenMEPackage(s, winForm: this); }
private void CompileIntoGame() { NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"MixinManager CompileIntoGameThread"); List <string> failedApplications = new List <string>(); nbw.DoWork += (a, b) => { BottomLeftMessage = M3L.GetString(M3L.string_compilingMixins); OperationInProgress = true; //DEBUG STUFF #if DEBUG int numCoresToApplyWith = 1; #else var numCoresToApplyWith = Environment.ProcessorCount; if (numCoresToApplyWith > 4) { numCoresToApplyWith = 4; //no more than 4 as this uses a lot of memory } #endif var mixins = AvailableOfficialMixins.Where(x => x.UISelectedForUse).ToList(); MixinHandler.LoadPatchDataForMixins(mixins); //before dynamic void failedApplicationCallback(string str) { failedApplications.Add(str); } var compilingListsPerModule = MixinHandler.GetMixinApplicationList(mixins, failedApplicationCallback); if (failedApplications.Any()) { //Error building list Log.Information(@"Aborting mixin install due to incompatible selection of mixins"); return; } ProgressBarMax = mixins.Count(); ProgressBarValue = 0; int numdone = 0; void completedSingleApplicationCallback() { var val = Interlocked.Increment(ref numdone); ProgressBarValue = val; } //Mixins are ready to be applied Parallel.ForEach(compilingListsPerModule, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount > numCoresToApplyWith ? numCoresToApplyWith : Environment.ProcessorCount }, mapping => { var dlcFolderName = ModMakerCompiler.ModmakerChunkNameToDLCFoldername(mapping.Key.ToString()); //var outdir = Path.Combine(modpath, ModMakerCompiler.HeaderToDefaultFoldername(mapping.Key), @"CookedPCConsole"); //Directory.CreateDirectory(outdir); if (mapping.Key == ModJob.JobHeader.BASEGAME) { //basegame foreach (var file in mapping.Value) { try { using var vanillaPackageAsStream = VanillaDatabaseService.FetchBasegameFile(Mod.MEGame.ME3, Path.GetFileName(file.Key)); //packageAsStream.WriteToFile(@"C:\users\dev\desktop\compressed.pcc"); using var decompressedStream = MEPackage.GetDecompressedPackageStream(vanillaPackageAsStream, true); decompressedStream.Position = 0; var vanillaPackage = MEPackageHandler.OpenMEPackage(decompressedStream, $@"Vanilla - {Path.GetFileName(file.Key)}"); //decompressedStream.WriteToFile(@"C:\users\dev\desktop\decompressed.pcc"); using var mixinModifiedStream = MixinHandler.ApplyMixins(decompressedStream, file.Value, completedSingleApplicationCallback, failedApplicationCallback); mixinModifiedStream.Position = 0; var modifiedPackage = MEPackageHandler.OpenMEPackage(mixinModifiedStream, $@"Mixin Modified - {Path.GetFileName(file.Key)}"); // three way merge: get target stream var targetFile = Path.Combine(MEDirectories.CookedPath(SelectedInstallTarget), Path.GetFileName(file.Key)); var targetPackage = MEPackageHandler.OpenMEPackage(targetFile); var merged = ThreeWayPackageMerge.AttemptMerge(vanillaPackage, modifiedPackage, targetPackage); if (merged) { targetPackage.save(); Log.Information("Three way merge succeeded for " + targetFile); } else { Log.Error("Could not merge three way merge into " + targetFile); } //var outfile = Path.Combine(outdir, Path.GetFileName(file.Key)); //package.save(outfile, false); // don't compress //finalStream.WriteToFile(outfile); //File.WriteAllBytes(outfile, finalStream.ToArray()); } catch (Exception e) { var mixinsStr = string.Join(@", ", file.Value.Select(x => x.PatchName)); Log.Error($@"Error in mixin application for file {file.Key}: {e.Message}"); failedApplicationCallback(M3L.GetString(M3L.string_interp_errorApplyingMixinsForFile, mixinsStr, file.Key, e.Message)); } } } else { //dlc var dlcPackage = VanillaDatabaseService.FetchVanillaSFAR(dlcFolderName); //do not have to open file multiple times. var targetCookedPCDir = Path.Combine(MEDirectories.DLCPath(SelectedInstallTarget), dlcFolderName, @"CookedPCConsole"); var sfar = mapping.Key == ModJob.JobHeader.TESTPATCH ? Utilities.GetTestPatchPath(SelectedInstallTarget) : Path.Combine(targetCookedPCDir, @"Default.sfar"); bool unpacked = new FileInfo(sfar).Length == 32; DLCPackage targetDLCPackage = unpacked ? null : new DLCPackage(sfar); //cache SFAR target foreach (var file in mapping.Value) { try { using var vanillaPackageAsStream = VanillaDatabaseService.FetchFileFromVanillaSFAR(dlcFolderName, file.Key, forcedDLC: dlcPackage); using var decompressedStream = MEPackage.GetDecompressedPackageStream(vanillaPackageAsStream); decompressedStream.Position = 0; var vanillaPackage = MEPackageHandler.OpenMEPackage(decompressedStream, $@"VanillaDLC - {Path.GetFileName(file.Key)}"); using var mixinModifiedStream = MixinHandler.ApplyMixins(decompressedStream, file.Value, completedSingleApplicationCallback, failedApplicationCallback); mixinModifiedStream.Position = 0; var modifiedPackage = MEPackageHandler.OpenMEPackage(mixinModifiedStream, $@"Mixin Modified - {Path.GetFileName(file.Key)}"); // three way merge: get target stream // must see if DLC is unpacked first MemoryStream targetFileStream = null; //Packed if (unpacked) { targetFileStream = new MemoryStream(File.ReadAllBytes(Path.Combine(targetCookedPCDir, file.Key))); } else { targetFileStream = VanillaDatabaseService.FetchFileFromVanillaSFAR(dlcFolderName, Path.GetFileName(file.Key), forcedDLC: targetDLCPackage); } var targetPackage = MEPackageHandler.OpenMEPackage(targetFileStream, $@"Target package {dlcFolderName} - {file.Key}, from SFAR: {unpacked}"); var merged = ThreeWayPackageMerge.AttemptMerge(vanillaPackage, modifiedPackage, targetPackage); if (merged) { if (unpacked) { targetPackage.save(); Log.Information("Three way merge succeeded for " + targetPackage.FilePath); } else { var finalSTream = targetPackage.saveToStream(); targetDLCPackage.ReplaceEntry(finalSTream.ToArray(), targetDLCPackage.FindFileEntry(Path.GetFileName(file.Key))); Log.Information("Three way merge succeeded for " + targetPackage.FileSourceForDebugging); } } else { Log.Error("Could not merge three way merge into " + targetFileStream); } } catch (Exception e) { var mixinsStr = string.Join(@", ", file.Value.Select(x => x.PatchName)); Log.Error($@"Error in mixin application for file {file.Key}: {e.Message}"); failedApplicationCallback(M3L.GetString(M3L.string_interp_errorApplyingMixinsForFile, mixinsStr, file.Key, e.Message)); } //finalStream.WriteToFile(outfile); } } }); MixinHandler.FreeME3TweaksPatchData(); var percent = 0; //this is used to save a localization BottomLeftMessage = $"Running AutoTOC on game {percent}%"; //Run autotoc void tocingUpdate(int percent) { BottomLeftMessage = $"Running AutoTOC on game {percent}%"; } AutoTOC.RunTOCOnGameTarget(SelectedInstallTarget, tocingUpdate); //Generate moddesc //IniData ini = new IniData(); //ini[@"ModManager"][@"cmmver"] = App.HighestSupportedModDesc.ToString(CultureInfo.InvariantCulture); //prevent commas //ini[@"ModInfo"][@"game"] = @"ME3"; //ini[@"ModInfo"][@"modname"] = modname; //ini[@"ModInfo"][@"moddev"] = App.AppVersionHR; //ini[@"ModInfo"][@"moddesc"] = M3L.GetString(M3L.string_compiledFromTheFollowingMixins); //ini[@"ModInfo"][@"modver"] = @"1.0"; //generateRepaceFilesMapping(ini, modpath); //File.WriteAllText(Path.Combine(modpath, @"moddesc.ini"), ini.ToString()); }; nbw.RunWorkerCompleted += (a, b) => { OperationInProgress = false; ClearMixinHandler(); if (failedApplications.Count > 0) { var ld = new ListDialog(failedApplications, M3L.GetString(M3L.string_failedToApplyAllMixins), M3L.GetString(M3L.string_theFollowingMixinsFailedToApply), mainwindow); ld.ShowDialog(); } /*if (modpath != null) * { * OnClosing(new DataEventArgs(modpath)); * } * else * {*/ BottomLeftMessage = "Mixins installed, maybe. Check logs"; //} }; CompilePanelButton.IsOpen = false; nbw.RunWorkerAsync(); }
public MeshExporter(string pccPath) { MEPackageHandler.Initialize(); Pcc = MEPackageHandler.OpenMEPackage(pccPath); }
public void TestNameOperations() { GlobalTest.Init(); Random random = new Random(); // 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()) { // Do not use package caching in tests Console.WriteLine($"Opening package {p}"); (var game, var platform) = GlobalTest.GetExpectedTypes(p); if (platform == MEPackage.GamePlatform.PC) // Will expand in future, but not now. { var loadedPackage = MEPackageHandler.OpenMEPackage(p, forceLoadFromDisk: true); var afterLoadNameCount = loadedPackage.NameCount; for (int i = 0; i < afterLoadNameCount; i++) { var existingName = loadedPackage.Names[i]; var existingNameIndex = loadedPackage.FindNameOrAdd(existingName); Assert.IsTrue(existingNameIndex == i, "An existing name was added when it shouldn't have been!"); } // Test adding for (int i = 0; i < 20; i++) { var expectedNameIndex = loadedPackage.NameCount; var newName = RandomString(random, 35); // If we have a same-collision on 35 char random strings, let Mgamerz know he should buy a lottery ticket var newNameIndex = loadedPackage.FindNameOrAdd(newName); Assert.AreEqual(expectedNameIndex, newNameIndex, "A name was added, but the index lookup was wrong!"); } // Test changing for (int i = 0; i < 20; i++) { var existingIndex = random.Next(loadedPackage.NameCount); var newName = RandomString(random, 38); //even more entropy loadedPackage.replaceName(existingIndex, newName); // Check it's correct var calculatedIndex = loadedPackage.FindNameOrAdd(newName); Assert.AreEqual(existingIndex, calculatedIndex, "A name was replaced, but the index of the replaced name was wrong when looked up via FindNameOrAdd()!"); var checkedNameGet = loadedPackage.GetNameEntry(calculatedIndex); var checkedNameAccessor = loadedPackage.GetNameEntry(calculatedIndex); Assert.AreEqual(newName, checkedNameGet, "A name was replaced, but the GetNameEntry() for the replaced name returned the wrong name!"); Assert.AreEqual(newName, checkedNameAccessor, "A name was replaced, but the Names[] array accessor for the replaced name returned the wrong name!"); } } } } }
public void TestCompression() { 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()) { // Do not use package caching in tests Console.WriteLine($"Opening package {p}"); var originalLoadedPackage = MEPackageHandler.OpenMEPackage(p, forceLoadFromDisk: true); if (originalLoadedPackage.Platform != MEPackage.GamePlatform.PC) { Assert.ThrowsException <Exception>(() => { originalLoadedPackage.SaveToStream(true); }, "Non-PC platform package should not be saveable. An exception should have been thrown to stop this!"); continue; } // Is PC var uncompressedPS = originalLoadedPackage.SaveToStream(false); var compressedPS = originalLoadedPackage.SaveToStream(true); uncompressedPS.Position = compressedPS.Position = 0; var reopenedUCP = MEPackageHandler.OpenMEPackageFromStream(uncompressedPS); var reopenedCCP = MEPackageHandler.OpenMEPackageFromStream(compressedPS); Assert.AreEqual(reopenedCCP.NameCount, reopenedUCP.NameCount, $"Name count is not identical between compressed/uncompressed packages"); Assert.AreEqual(reopenedCCP.ImportCount, reopenedUCP.ImportCount, $"Import count is not identical between compressed/uncompressed packages"); Assert.AreEqual(reopenedCCP.ExportCount, reopenedUCP.ExportCount, $"Export count is not identical between compressed/uncompressed packages"); for (int i = 0; i < reopenedCCP.NameCount; i++) { var nameCCP = reopenedCCP.Names[i]; var nameUCP = reopenedUCP.Names[i]; Assert.AreEqual(nameCCP, nameUCP, $"Names are not identical between compressed/uncompressed packages, name index {i}"); } for (int i = 0; i < reopenedCCP.ImportCount; i++) { var importCCP = reopenedCCP.Imports[i]; var importUCP = reopenedUCP.Imports[i]; Assert.IsTrue(importCCP.Header.SequenceEqual(importUCP.Header), $"Header data for import {-(i + 1)} are not identical between compressed/uncompressed packages"); } for (int i = 0; i < reopenedCCP.ExportCount; i++) { var exportCCP = reopenedCCP.Exports[i]; var exportUCP = reopenedUCP.Exports[i]; Assert.IsTrue(exportCCP.Header.SequenceEqual(exportUCP.Header), $"Header data for xport {i + 1} are not identical between compressed/uncompressed packages"); } } } }
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); } } }
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 Mod GenerateCompatibilityPackForFiles(List <string> dlcModList, List <string> filesToBePatched, SevenZipExtractor uiArchive) { dlcModList = dlcModList.Select(x => { var tpmi = ThirdPartyServices.GetThirdPartyModInfo(x, MEGame.ME3); if (tpmi == null) { return(x); } return(tpmi.modname); }).ToList(); string dlcs = string.Join(@"\n - ", dlcModList); StarterKitOptions sko = new StarterKitOptions { ModName = M3L.GetString(M3L.string_guiCompatibilityPack), ModDescription = M3L.GetString(M3L.string_interp_compatPackModDescription, dlcs, DateTime.Now.ToString()), ModDeveloper = App.AppVersionHR, ModDLCFolderName = UI_MOD_NAME, ModGame = MEGame.ME3, ModInternalName = @"UI Mod Compatibility Pack", ModInternalTLKID = 1420400890, ModMountFlag = EMountFileFlag.ME3_SPOnly_NoSaveFileDependency, ModMountPriority = 31050, ModURL = null, ModModuleNumber = 0 }; Mod generatedMod = null; void modGenerated(Mod mod) { generatedMod = mod; lock (modGeneratedSignaler) { Monitor.Pulse(modGeneratedSignaler); } } void skUicallback(string text) { ActionSubstring = text; } StarterKitGeneratorWindow.CreateStarterKitMod(sko, skUicallback, modGenerated); lock (modGeneratedSignaler) { Monitor.Wait(modGeneratedSignaler); } //Mod has been generated. string outputPath = Path.Combine(generatedMod.ModPath, @"DLC_MOD_" + UI_MOD_NAME, @"CookedPCConsole"); ActionString = M3L.GetString(M3L.string_preparingUiLibrary); ActionSubstring = M3L.GetString(M3L.string_decompressingData); int done = 0; CaseInsensitiveDictionary <byte[]> uiLibraryData = new CaseInsensitiveDictionary <byte[]>(); var filesToDecompress = uiArchive.ArchiveFileData.Where(x => x.FileName.EndsWith(@".swf")).ToList(); foreach (var f in filesToDecompress) { MemoryStream decompressedStream = new MemoryStream(); uiArchive.ExtractFile(f.Index, decompressedStream); string fname = f.FileName.Substring(f.FileName.IndexOf('\\') + 1); fname = fname.Substring(0, fname.IndexOf(@".swf", StringComparison.InvariantCultureIgnoreCase)); uiLibraryData[fname] = decompressedStream.ToArray(); done++; Percent = getPercent(done, filesToDecompress.Count); } ActionString = M3L.GetString(M3L.string_patchingFiles); Percent = 0; done = 0; string singlesuffix = M3L.GetString(M3L.string_singularFile); string pluralsuffix = M3L.GetString(M3L.string_pluralFiles); foreach (var file in filesToBePatched) { ActionSubstring = Path.GetFileName(file); var package = MEPackageHandler.OpenMEPackage(file); var guiExports = package.Exports.Where(x => !x.IsDefaultObject && x.ClassName == @"GFxMovieInfo").ToList(); if (guiExports.Count > 0) { //potential item needing replacement //Check GUI library to see if we have anything. foreach (var export in guiExports) { if (uiLibraryData.TryGetValue(export.GetFullPath, out var newData)) { //Patching this export. var exportProperties = export.GetProperties(); var rawData = exportProperties.GetProp <ArrayProperty <ByteProperty> >(@"RawData"); rawData.Clear(); rawData.AddRange(newData.Select(x => new ByteProperty(x))); //This will be terribly slow. Need to port over new ME3Exp binary data handler export.WriteProperties(exportProperties); } else { Debug.WriteLine(@"Not patching gui export, file not in library: " + export.GetFullPath); } } var outpath = Path.Combine(outputPath, Path.GetFileName(package.FilePath)); if (package.IsModified) { Log.Information(@"Saving patched package to " + outpath); package.save(outpath); done++; ActionSubstring = M3L.GetString(M3L.string_interp_patchedXY, done.ToString(), done == 1 ? singlesuffix : pluralsuffix); } else { done++; Log.Information(@"File was patched but data did not change! " + outpath); } Percent = getPercent(done, filesToBePatched.Count); } else { throw new Exception($@"Error: {Path.GetFileName(file)} doesn't contain GUI exports! This shouldn't have been possible."); } } return(generatedMod); }
/// <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); } }
private static void RandomizeAfterlifeShepDance() { var denDanceF = MERFileSystem.GetPackageFile(@"BioD_OmgHub_230DenDance.pcc"); if (denDanceF != null) { var loungeP = MEPackageHandler.OpenMEPackage(denDanceF); var sequence = loungeP.GetUExport(3924); MERPackageCache cache = new MERPackageCache(); List <InterpTools.InterpData> interpDatas = new List <InterpTools.InterpData>(); var interp1 = loungeP.GetUExport(3813); // Make 2 additional dance options by cloning the interp and the data tree var interp2 = MERSeqTools.CloneBasicSequenceObject(interp1); var interp3 = MERSeqTools.CloneBasicSequenceObject(interp1); // Clone the interp data for attaching to 2 and 3 var interpData1 = loungeP.GetUExport(1174); var interpData2 = EntryCloner.CloneTree(interpData1); var interpData3 = EntryCloner.CloneTree(interpData2); KismetHelper.AddObjectToSequence(interpData2, sequence); KismetHelper.AddObjectToSequence(interpData3, sequence); // Load ID for randomization interpDatas.Add(new InterpTools.InterpData(interpData1)); interpDatas.Add(new InterpTools.InterpData(interpData2)); interpDatas.Add(new InterpTools.InterpData(interpData3)); // Chance the data for interp2/3 var id2 = SeqTools.GetVariableLinksOfNode(interp2); id2[0].LinkedNodes[0] = interpData2; SeqTools.WriteVariableLinksToNode(interp2, id2); var id3 = SeqTools.GetVariableLinksOfNode(interp3); id3[0].LinkedNodes[0] = interpData3; SeqTools.WriteVariableLinksToNode(interp3, id3); // Add additional finished states for fadetoblack when done KismetHelper.CreateOutputLink(loungeP.GetUExport(958), "Finished", interp2, 2); KismetHelper.CreateOutputLink(loungeP.GetUExport(958), "Finished", interp3, 2); // Link up the random choice it makes var randSw = MERSeqTools.InstallRandomSwitchIntoSequence(sequence, 3); KismetHelper.CreateOutputLink(randSw, "Link 1", interp1); KismetHelper.CreateOutputLink(randSw, "Link 2", interp2); KismetHelper.CreateOutputLink(randSw, "Link 3", interp3); // Break the original output to start the interp, repoint it's output to the switch instead var sgm = loungeP.GetUExport(1003); //set gesture mode KismetHelper.RemoveOutputLinks(sgm); KismetHelper.CreateOutputLink(sgm, "Done", loungeP.GetUExport(960)); KismetHelper.CreateOutputLink(sgm, "Done", randSw); // Now install the dances foreach (var id in interpDatas) { var danceTrack = id.InterpGroups[0].Tracks[0]; OmegaHub.InstallShepardDanceGesture(danceTrack.Export, cache); } MERFileSystem.SavePackage(loungeP); } }
/// <summary> /// Extracts the mod from the archive. The caller should handle exception that may be thrown. /// </summary> /// <param name="archivePath"></param> /// <param name="outputFolderPath"></param> /// <param name="compressPackages"></param> /// <param name="updateTextCallback"></param> /// <param name="extractingCallback"></param> /// <param name="compressedPackageCallback"></param> /// <param name="testRun"></param> public void ExtractFromArchive(string archivePath, string outputFolderPath, bool compressPackages, Action <string> updateTextCallback = null, Action <DetailedProgressEventArgs> extractingCallback = null, Action <string, int, int> compressedPackageCallback = null, bool testRun = false, Stream archiveStream = null) { if (!IsInArchive) { throw new Exception(@"Cannot extract a mod that is not part of an archive."); } if (archiveStream == null && !File.Exists(archivePath)) { throw new Exception(M3L.GetString(M3L.string_interp_theArchiveFileArchivePathIsNoLongerAvailable, archivePath)); } compressPackages &= Game >= MEGame.ME2; SevenZipExtractor archive; var isExe = archivePath.EndsWith(@".exe", StringComparison.InvariantCultureIgnoreCase); bool closeStreamOnFinish = true; if (archiveStream != null) { archive = isExe ? new SevenZipExtractor(archiveStream, InArchiveFormat.Nsis) : new SevenZipExtractor(archiveStream); closeStreamOnFinish = false; } else { archive = isExe ? new SevenZipExtractor(archivePath, InArchiveFormat.Nsis) : new SevenZipExtractor(archivePath); } var fileIndicesToExtract = new List <int>(); var filePathsToExtractTESTONLY = new List <string>(); var referencedFiles = GetAllRelativeReferences(!IsVirtualized, archive); if (isExe) { //remap to mod root. Not entirely sure if this needs to be done for sub mods? referencedFiles = referencedFiles.Select(x => FilesystemInterposer.PathCombine(IsInArchive, ModPath, x)).ToList(); //remap to in-archive paths so they match entry paths } foreach (var info in archive.ArchiveFileData) { if (!info.IsDirectory && (ModPath == "" || info.FileName.Contains((string)ModPath))) { var relativedName = isExe ? info.FileName : info.FileName.Substring(ModPath.Length).TrimStart('\\'); if (referencedFiles.Contains(relativedName)) { Log.Information(@"Adding file to extraction list: " + info.FileName); fileIndicesToExtract.Add(info.Index); filePathsToExtractTESTONLY.Add(relativedName); } } } void archiveExtractionProgress(object?sender, DetailedProgressEventArgs args) { extractingCallback?.Invoke(args); } archive.Progressing += archiveExtractionProgress; string outputFilePathMapping(ArchiveFileInfo entryInfo) { Log.Information(@"Mapping extraction target for " + entryInfo.FileName); string entryPath = entryInfo.FileName; if (ExeExtractionTransform != null && ExeExtractionTransform.PatchRedirects.Any(x => x.index == entryInfo.Index)) { Log.Information(@"Extracting vpatch file at index " + entryInfo.Index); return(Path.Combine(Utilities.GetVPatchRedirectsFolder(), ExeExtractionTransform.PatchRedirects.First(x => x.index == entryInfo.Index).outfile)); } if (ExeExtractionTransform != null && ExeExtractionTransform.NoExtractIndexes.Any(x => x == entryInfo.Index)) { Log.Information(@"Extracting file to trash (not used): " + entryPath); return(Path.Combine(Utilities.GetTempPath(), @"Trash", @"trashfile")); } if (ExeExtractionTransform != null && ExeExtractionTransform.AlternateRedirects.Any(x => x.index == entryInfo.Index)) { var outfile = ExeExtractionTransform.AlternateRedirects.First(x => x.index == entryInfo.Index).outfile; Log.Information($@"Extracting file with redirection: {entryPath} -> {outfile}"); return(Path.Combine(outputFolderPath, outfile)); } //Archive path might start with a \. Substring may return value that start with a \ var subModPath = entryPath /*.TrimStart('\\')*/.Substring(ModPath.Length).TrimStart('\\'); var path = Path.Combine(outputFolderPath, subModPath); //Debug.WriteLine("remapping output: " + entryPath + " -> " + path); return(path); } if (compressPackages) { compressionQueue = new BlockingCollection <string>(); } int numberOfPackagesToCompress = referencedFiles.Count(x => x.RepresentsPackageFilePath()); int compressedPackageCount = 0; NamedBackgroundWorker compressionThread; if (compressPackages) { compressionThread = new NamedBackgroundWorker(@"ImportingCompressionThread"); compressionThread.DoWork += (a, b) => { try { while (true) { var package = compressionQueue.Take(); var p = MEPackageHandler.OpenMEPackage(package); bool shouldNotCompress = Game == MEGame.ME1; if (!shouldNotCompress) { //updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package))); FileInfo fileInfo = new FileInfo(package); var created = fileInfo.CreationTime; //File Creation var lastmodified = fileInfo.LastWriteTime; //File Modification compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress); Log.Information(@"Compressing package: " + package); p.Save(compress: true); File.SetCreationTime(package, created); File.SetLastWriteTime(package, lastmodified); } else { Log.Information(@"Skipping compression for ME1 package file: " + package); } Interlocked.Increment(ref compressedPackageCount); compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressedX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress); } } catch (InvalidOperationException) { //Done. lock (compressionCompletedSignaler) { Monitor.Pulse(compressionCompletedSignaler); } } }; compressionThread.RunWorkerAsync(); } void compressPackage(object?sender, FileInfoEventArgs args) { if (compressPackages) { var fToCompress = outputFilePathMapping(args.FileInfo); if (fToCompress.RepresentsPackageFilePath()) { //Debug.WriteLine("Adding to blocking queue"); compressionQueue.TryAdd(fToCompress); } } } archive.FileExtractionFinished += compressPackage; if (!testRun) { Log.Information(@"Extracting files..."); archive.ExtractFiles(outputFolderPath, outputFilePathMapping, fileIndicesToExtract.ToArray()); } else { // test run mode // exes can have duplicate filenames but different indexes so we must check for those here. if (fileIndicesToExtract.Count != referencedFiles.Count && filePathsToExtractTESTONLY.Distinct().ToList().Count != referencedFiles.Count) { throw new Exception(@"The amount of referenced files does not match the amount of files that are going to be extracted!"); } } Log.Information(@"File extraction completed."); archive.Progressing -= archiveExtractionProgress; compressionQueue?.CompleteAdding(); if (compressPackages && numberOfPackagesToCompress > 0 && numberOfPackagesToCompress > compressedPackageCount) { Log.Information(@"Waiting for compression of packages to complete."); while (!compressionQueue.IsCompleted) { lock (compressionCompletedSignaler) { Monitor.Wait(compressionCompletedSignaler); } } Log.Information(@"Package compression has completed."); } archive.FileExtractionFinished -= compressPackage; ModPath = outputFolderPath; if (IsVirtualized) { var parser = new IniDataParser().Parse(VirtualizedIniText); parser[@"ModInfo"][@"modver"] = ModVersionString; //In event relay service resolved this if (!testRun) { File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), parser.ToString()); } IsVirtualized = false; //no longer virtualized } if (ExeExtractionTransform != null) { if (ExeExtractionTransform.VPatches.Any()) { // MEHEM uses Vpatching for its alternates. var vpat = Utilities.GetCachedExecutablePath(@"vpat.exe"); if (!testRun) { Utilities.ExtractInternalFile(@"MassEffectModManagerCore.modmanager.executables.vpat.exe", vpat, true); } //Handle VPatching foreach (var transform in ExeExtractionTransform.VPatches) { var patchfile = Path.Combine(Utilities.GetVPatchRedirectsFolder(), transform.patchfile); var inputfile = Path.Combine(ModPath, transform.inputfile); var outputfile = Path.Combine(ModPath, transform.outputfile); var args = $"\"{patchfile}\" \"{inputfile}\" \"{outputfile}\""; //do not localize if (!testRun) { Directory.CreateDirectory(Directory.GetParent(outputfile).FullName); //ensure output directory exists as vpatch will not make one. } Log.Information($@"VPatching file into alternate: {inputfile} to {outputfile}"); updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_vPatchingIntoAlternate, Path.GetFileName(inputfile))); if (!testRun) { Utilities.RunProcess(vpat, args, true, false, false, true); } } } //Handle copyfile foreach (var copyfile in ExeExtractionTransform.CopyFiles) { string srcfile = Path.Combine(ModPath, copyfile.inputfile); string destfile = Path.Combine(ModPath, copyfile.outputfile); Log.Information($@"Applying transform copyfile: {srcfile} -> {destfile}"); if (!testRun) { File.Copy(srcfile, destfile, true); } } if (ExeExtractionTransform.PostTransformModdesc != null) { //fetch online moddesc for this mod. Log.Information(@"Fetching post-transform third party moddesc."); var moddesc = OnlineContent.FetchThirdPartyModdesc(ExeExtractionTransform.PostTransformModdesc); if (!testRun) { File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), moddesc); } } } //int packagesCompressed = 0; //if (compressPackages) //{ // var packages = Utilities.GetPackagesInDirectory(ModPath, true); // extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0)); // foreach (var package in packages) // { // updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package))); // Log.Information("Compressing package: " + package); // var p = MEPackageHandler.OpenMEPackage(package); // p.save(true); // packagesCompressed++; // extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0)); // } //} if (closeStreamOnFinish) { archive?.Dispose(); } else { archive?.DisposeObjectOnly(); } }
public void LoadMEPackage(string s) { pcc?.Release(wpfWindow: this); pcc = MEPackageHandler.OpenMEPackage(s, wpfWindow: this); }
private void CompileAsNewMod() { NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"MixinManager CompileAsNewModThread"); List <string> failedApplications = new List <string>(); var modname = NewModName; var modpath = Path.Combine(Utilities.GetME3ModsDirectory(), Utilities.SanitizePath(modname)); var result = M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_dialogCreatingNewModWithExistingName, NewModName, modpath), M3L.GetString(M3L.string_modAlreadyExists), MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No); if (result == MessageBoxResult.No) { Log.Information(@"User has aborted mixin compilation due to same-named mod existing"); return; //abort. } nbw.DoWork += (a, b) => { BottomLeftMessage = M3L.GetString(M3L.string_compilingMixins); OperationInProgress = true; //DEBUG STUFF #if DEBUG int numCoresToApplyWith = 1; #else var numCoresToApplyWith = Environment.ProcessorCount; if (numCoresToApplyWith > 4) { numCoresToApplyWith = 4; //no more than 4 as this uses a lot of memory } #endif var mixins = AvailableOfficialMixins.Where(x => x.UISelectedForUse).ToList(); MixinHandler.LoadPatchDataForMixins(mixins); //before dynamic void failedApplicationCallback(string str) { failedApplications.Add(str); } var compilingListsPerModule = MixinHandler.GetMixinApplicationList(mixins, failedApplicationCallback); if (failedApplications.Any()) { //Error building list modpath = null; Log.Information(@"Aborting mixin compiling due to incompatible selection of mixins"); return; } if (Directory.Exists(modpath)) { Utilities.DeleteFilesAndFoldersRecursively(modpath); } ProgressBarMax = mixins.Count(); ProgressBarValue = 0; int numdone = 0; void completedSingleApplicationCallback() { var val = Interlocked.Increment(ref numdone); ProgressBarValue = val; } //Mixins are ready to be applied Parallel.ForEach(compilingListsPerModule, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount > numCoresToApplyWith ? numCoresToApplyWith : Environment.ProcessorCount }, mapping => { var dlcFolderName = ModMakerCompiler.ModmakerChunkNameToDLCFoldername(mapping.Key.ToString()); var outdir = Path.Combine(modpath, ModMakerCompiler.HeaderToDefaultFoldername(mapping.Key), @"CookedPCConsole"); Directory.CreateDirectory(outdir); if (mapping.Key == ModJob.JobHeader.BASEGAME) { //basegame foreach (var file in mapping.Value) { try { using var packageAsStream = VanillaDatabaseService.FetchBasegameFile(Mod.MEGame.ME3, Path.GetFileName(file.Key)); //packageAsStream.WriteToFile(@"C:\users\dev\desktop\compressed.pcc"); using var decompressedStream = MEPackage.GetDecompressedPackageStream(packageAsStream, true); //decompressedStream.WriteToFile(@"C:\users\dev\desktop\decompressed.pcc"); using var finalStream = MixinHandler.ApplyMixins(decompressedStream, file.Value, completedSingleApplicationCallback, failedApplicationCallback); CLog.Information(@"Compressing package to mod directory: " + file.Key, Settings.LogModMakerCompiler); finalStream.Position = 0; var package = MEPackageHandler.OpenMEPackage(finalStream); var outfile = Path.Combine(outdir, Path.GetFileName(file.Key)); package.save(outfile, false); // don't compress //finalStream.WriteToFile(outfile); //File.WriteAllBytes(outfile, finalStream.ToArray()); } catch (Exception e) { var mixinsStr = string.Join(@", ", file.Value.Select(x => x.PatchName)); Log.Error($@"Error in mixin application for file {file.Key}: {e.Message}"); failedApplicationCallback(M3L.GetString(M3L.string_interp_errorApplyingMixinsForFile, mixinsStr, file.Key, e.Message)); } } } else { //dlc var dlcPackage = VanillaDatabaseService.FetchVanillaSFAR(dlcFolderName); //do not have to open file multiple times. foreach (var file in mapping.Value) { try { using var packageAsStream = VanillaDatabaseService.FetchFileFromVanillaSFAR(dlcFolderName, file.Key, forcedDLC: dlcPackage); using var decompressedStream = MEPackage.GetDecompressedPackageStream(packageAsStream); using var finalStream = MixinHandler.ApplyMixins(decompressedStream, file.Value, completedSingleApplicationCallback, failedApplicationCallback); CLog.Information(@"Compressing package to mod directory: " + file.Key, Settings.LogModMakerCompiler); finalStream.Position = 0; var package = MEPackageHandler.OpenMEPackage(finalStream); var outfile = Path.Combine(outdir, Path.GetFileName(file.Key)); package.save(outfile, true); } catch (Exception e) { var mixinsStr = string.Join(@", ", file.Value.Select(x => x.PatchName)); Log.Error($@"Error in mixin application for file {file.Key}: {e.Message}"); failedApplicationCallback(M3L.GetString(M3L.string_interp_errorApplyingMixinsForFile, mixinsStr, file.Key, e.Message)); } //finalStream.WriteToFile(outfile); } } }); MixinHandler.FreeME3TweaksPatchData(); //Generate moddesc IniData ini = new IniData(); ini[@"ModManager"][@"cmmver"] = App.HighestSupportedModDesc.ToString(CultureInfo.InvariantCulture); //prevent commas ini[@"ModInfo"][@"game"] = @"ME3"; ini[@"ModInfo"][@"modname"] = modname; ini[@"ModInfo"][@"moddev"] = App.AppVersionHR; ini[@"ModInfo"][@"moddesc"] = M3L.GetString(M3L.string_compiledFromTheFollowingMixins); ini[@"ModInfo"][@"modver"] = @"1.0"; generateRepaceFilesMapping(ini, modpath); File.WriteAllText(Path.Combine(modpath, @"moddesc.ini"), ini.ToString()); }; nbw.RunWorkerCompleted += (a, b) => { OperationInProgress = false; ClearMixinHandler(); if (failedApplications.Count > 0) { var ld = new ListDialog(failedApplications, M3L.GetString(M3L.string_failedToApplyAllMixins), M3L.GetString(M3L.string_theFollowingMixinsFailedToApply), mainwindow); ld.ShowDialog(); } if (modpath != null) { OnClosing(new DataEventArgs(modpath)); } else { BottomLeftMessage = M3L.GetString(M3L.string_selectMixinsToCompile); } }; CompilePanelButton.IsOpen = false; nbw.RunWorkerAsync(); }
private void LoadFile(string fileName) { string lowerFilename = Path.GetExtension(fileName).ToLower(); if (lowerFilename.EndsWith(".pcc") || lowerFilename.EndsWith(".u") || lowerFilename.EndsWith(".sfm") || lowerFilename.EndsWith(".upk")) { pcc = MEPackageHandler.OpenMEPackage(fileName); } bytes = File.ReadAllBytes(fileName); Interpreter_Hexbox.ByteProvider = new DynamicByteProvider(bytes); Title = "FileHexViewerWPF - " + fileName; AddRecent(fileName, false); SaveRecentList(); RefreshRecent(); MemoryStream inStream = new MemoryStream(bytes); UnusedSpaceList.ClearEx(); if (pcc != null) { List <UsedSpace> used = new List <UsedSpace>(); used.Add(new UsedSpace { UsedFor = "Package Header", UsedSpaceStart = 0, UsedSpaceEnd = pcc.NameOffset }); inStream.Seek(pcc.NameOffset, SeekOrigin.Begin); for (int i = 0; i < pcc.NameCount; i++) { int strLength = inStream.ReadInt32(); if (strLength < 0) { inStream.ReadStringUnicodeNull(strLength * -2); if (pcc.Game == MEGame.ME2) { inStream.ReadInt32(); } } else if (strLength > 0) { inStream.ReadStringASCIINull(strLength); //-1 cause we also read trailing null. if (pcc.Game != MEGame.ME2) { inStream.ReadInt64(); //Read 8 bytes } else { inStream.ReadInt32(); //4 bytes } } } used.Add(new UsedSpace { UsedFor = "Name Table", UsedSpaceStart = pcc.NameOffset, UsedSpaceEnd = (int)inStream.Position }); for (int i = 0; i < pcc.ImportCount; i++) { inStream.Position += 28; } used.Add(new UsedSpace { UsedFor = "Import Table", UsedSpaceStart = pcc.ImportOffset, UsedSpaceEnd = (int)inStream.Position }); inStream.Seek(pcc.ExportOffset, SeekOrigin.Begin); foreach (ExportEntry exp in pcc.Exports) { inStream.Position += exp.Header.Length; } used.Add(new UsedSpace { UsedFor = "Export Metadata Table", UsedSpaceStart = pcc.ExportOffset, UsedSpaceEnd = (int)inStream.Position }); used.Add(new UsedSpace { UsedFor = "Dependency Table (Unused)", UsedSpaceStart = ((MEPackage)pcc).DependencyTableOffset, UsedSpaceEnd = ((MEPackage)pcc).FullHeaderSize }); List <UsedSpace> usedExportsSpaces = new List <UsedSpace>(); inStream.Seek(pcc.ExportOffset, SeekOrigin.Begin); foreach (ExportEntry exp in pcc.Exports) { usedExportsSpaces.Add(new UsedSpace { UsedFor = $"Export {exp.UIndex} - {exp.ObjectName} ({exp.ClassName})", UsedSpaceStart = exp.DataOffset, UsedSpaceEnd = exp.DataOffset + exp.DataSize, Export = exp }); } usedExportsSpaces = usedExportsSpaces.OrderBy(x => x.UsedSpaceStart).ToList(); int endOffset = 0; List <UsedSpace> displayedUsedSpace = new List <UsedSpace>(); foreach (var usedSpace in usedExportsSpaces) { if (endOffset != 0 && usedSpace.UsedSpaceStart != endOffset) { //unused space displayedUsedSpace.Add(new UsedSpace() { UsedFor = "Unused space", UsedSpaceStart = endOffset, UsedSpaceEnd = usedSpace.UsedSpaceStart, Unused = true }); endOffset = usedSpace.UsedSpaceStart; } displayedUsedSpace.Add(usedSpace); endOffset = usedSpace.UsedSpaceEnd; } //List<UsedSpace> continuousBlocks = new List<UsedSpace>(); //UsedSpace continuous = new UsedSpace //{ // UsedFor = $"Continuous Export Data {usedExportsSpaces[0].Export.UIndex} {usedExportsSpaces[0].Export.ObjectName.Instanced} ({usedExportsSpaces[0].Export.ClassName})", // UsedSpaceStart = usedExportsSpaces[0].UsedSpaceStart, // UsedSpaceEnd = usedExportsSpaces[0].UsedSpaceEnd, // Export = usedExportsSpaces[0].Export //}; //for (int i = 1; i < usedExportsSpaces.Count; i++) //{ // UsedSpace u = usedExportsSpaces[i]; // if (continuous.UsedSpaceEnd == u.UsedSpaceStart) // { // continuous.UsedSpaceEnd = u.UsedSpaceEnd; // } // else // { // if (continuous.UsedSpaceEnd > u.UsedSpaceStart) // { // Debug.WriteLine("Possible overlap detected!"); // } // continuousBlocks.Add(continuous); // UsedSpace unused = new UsedSpace() // { // UsedFor = "Unused space", // UsedSpaceStart = continuous.UsedSpaceEnd, // UsedSpaceEnd = u.UsedSpaceStart, // Unused = true // }; // continuousBlocks.Add(unused); // continuous = new UsedSpace // { // UsedFor = $"Continuous Export Data {u.Export.UIndex} {u.Export.ObjectName.Instanced} ({u.Export.ClassName})", // UsedSpaceStart = u.UsedSpaceStart, // UsedSpaceEnd = u.UsedSpaceEnd, // Export = u.Export // }; // } //} //continuousBlocks.Add(continuous); UnusedSpaceList.AddRange(used); UnusedSpaceList.AddRange(displayedUsedSpace); } }
public static (List <ReferencedAudio> missingAFCReferences, List <ReferencedAudio> availableAFCReferences) GetReferencedAudio(MEGame game, string inputPath, Action <long, long> notifyProgress = null, Action <string> currentScanningFileCallback = null, Action <string> debugOut = null) { var sizesJsonStr = new StreamReader(Utilities.LoadFileFromCompressedResource("Infos.zip", $"{game}-vanillaaudiosizes.json")).ReadToEnd(); var vanillaSizesMap = JsonConvert.DeserializeObject <CaseInsensitiveDictionary <int> >(sizesJsonStr); var pccFiles = Directory.GetFiles(inputPath, "*.pcc", SearchOption.AllDirectories); AFCInventory afcInventory = AFCInventory.GetInventory(inputPath, game, debugOut); var referencedAFCAudio = new List <ReferencedAudio>(); var missingAFCReferences = new List <ReferencedAudio>(); int i = 1; foreach (string pccPath in pccFiles) { notifyProgress?.Invoke(i - 1, pccFiles.Count()); debugOut?.Invoke($@"SCANNING {pccPath}"); currentScanningFileCallback?.Invoke(pccPath); //NotifyStatusUpdate?.Invoke($"Finding all referenced audio ({i}/{pccFiles.Length})"); using (var pack = MEPackageHandler.OpenMEPackage(pccPath)) { List <ExportEntry> wwiseStreamExports = pack.Exports.Where(x => x.ClassName == "WwiseStream").ToList(); foreach (ExportEntry exp in wwiseStreamExports) { debugOut?.Invoke($@" >> WwiseStream {exp.UIndex} {exp.ObjectName}"); var afcNameProp = exp.GetProperty <NameProperty>("Filename"); if (afcNameProp != null) { debugOut?.Invoke($@" >>>> AFC filename: {afcNameProp.Value.Name}"); bool isBasegame = false; bool isOfficialDLC = false; var afcFile = afcInventory.LocalFolderAFCFiles.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x).Equals(afcNameProp.Value, StringComparison.InvariantCultureIgnoreCase)); bool logged = false; if (afcFile != null) { debugOut?.Invoke($@" >>>> AFC found locally: {afcFile}"); logged = true; } if (afcFile == null) { // Try to find basegame version afcFile = afcInventory.BasegameAFCFiles.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x).Equals(afcNameProp.Value, StringComparison.InvariantCultureIgnoreCase)); isBasegame = afcFile != null; } if (afcFile != null && !logged) { debugOut?.Invoke($@" >>>> AFC found in basegame: {afcFile}"); logged = true; } if (afcFile == null) { // Try to find official DLC version afcFile = afcInventory.OfficialDLCAFCFiles.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x).Equals(afcNameProp.Value, StringComparison.InvariantCultureIgnoreCase)); isOfficialDLC = afcFile != null; } if (afcFile != null && !logged) { debugOut?.Invoke($@" >>>> AFC found in official DLC: {afcFile}"); logged = true; } string afcName = afcNameProp.ToString().ToLower(); int audioSize = BitConverter.ToInt32(exp.Data, exp.Data.Length - 8); int audioOffset = BitConverter.ToInt32(exp.Data, exp.Data.Length - 4); bool isModified = false; if (afcFile != null) { var source = !isBasegame && !isOfficialDLC ? "Modified" : null; if (isBasegame || isOfficialDLC) { // Check if offset indicates this is official bioware afc territory if (vanillaSizesMap.TryGetValue(afcName, out var vanillaSize)) { if (audioOffset < vanillaSize) { if (isOfficialDLC) { source = "Official DLC"; } else if (isBasegame) { debugOut?.Invoke($" >>>> Dropping fully basegame audio reference: {exp.ObjectName}"); continue; //Fully basegame audio is never returned as it will always be available } } else { // Out of range? isModified = true; source = "Modified official DLC"; } } else { debugOut?.Invoke($"!!! Vanilla sizes map doesn't include file being checked: {afcName}"); } } else { isModified = true; source = "Modified unofficial"; } referencedAFCAudio.Add(new ReferencedAudio() { afcName = afcName, audioSize = audioSize, audioOffset = audioOffset, uiOriginatingExportName = exp.ObjectName, uiAFCSourceType = source, isModified = isModified }); } else { debugOut?.Invoke($@" !!!! AFC NOT FOUND: {afcNameProp.Value}"); missingAFCReferences.Add(new ReferencedAudio() { afcName = afcNameProp.Value, audioSize = audioSize, audioOffset = audioOffset, uiOriginatingExportName = exp.ObjectName, uiAFCSourceType = "AFC unavailable" }); } } } } i++; } referencedAFCAudio = referencedAFCAudio.Distinct().ToList(); return(missingAFCReferences, referencedAFCAudio); }
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.IsOrInheritsFrom(SequenceVariableName, game)) { ArrayProperty <StructProperty> varLinksProp = null; ArrayProperty <StructProperty> outLinksProp = null; ArrayProperty <StructProperty> eventLinksProp = null; Dictionary <string, ClassInfo> classes = UnrealObjectInfo.GetClasses(game); try { ClassInfo classInfo = info; while (classInfo != null && (varLinksProp == null || outLinksProp == null)) { string filepath = Path.Combine(MEDirectories.BioGamePath(game), classInfo.pccPath); if (File.Exists(classInfo.pccPath)) { filepath = classInfo.pccPath; //Used for dynamic lookup } else if (classInfo.pccPath == UnrealObjectInfo.Me3ExplorerCustomNativeAdditionsName) { filepath = App.CustomResourceFilePath(game); } else if (game == MEGame.ME1) { filepath = Path.Combine(ME1Directory.gamePath, classInfo.pccPath); //for files from ME1 DLC } if (File.Exists(filepath)) { using IMEPackage importPCC = MEPackageHandler.OpenMEPackage(filepath); 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) && EntryImporter.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) && EntryImporter.EnsureClassIsInFile(pcc, expectedVar.ObjectName) is IEntry portedExpectedVar) { expectedTypeProp.Value = portedExpectedVar.UIndex; } } } } } 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); } //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); } } int objInstanceVersion = UnrealObjectInfo.getSequenceObjectInfo(game, info.ClassName)?.ObjInstanceVersion ?? 1; defaults.Add(new IntProperty(objInstanceVersion, "ObjInstanceVersion")); return(defaults); }
public void AddFileQuick(string onDiskNewFile, string inArchivePath) { byte[] newFileBytes; if (Path.GetExtension(onDiskNewFile).ToLower() == @".pcc" && FileName.EndsWith(@"Patch_001.sfar", StringComparison.InvariantCultureIgnoreCase)) { //Use the decompressed bytes - SFARs can't store compressed packages apparently! var package = MEPackageHandler.OpenMEPackage(onDiskNewFile); newFileBytes = package.saveToStream().ToArray(); } else { newFileBytes = File.ReadAllBytes(onDiskNewFile); } FileStream sfarStream = new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); //Create Entry List <FileEntryStruct> tmp = new List <FileEntryStruct>(Files); FileEntryStruct e = new FileEntryStruct(); e.FileName = inArchivePath; e.BlockOffsets = new long[0]; e.Hash = ComputeHash(inArchivePath); e.BlockSizeIndex = 0xFFFFFFFF; e.UncompressedSize = (uint)newFileBytes.Length; e.UncompressedSizeAdder = 0; tmp.Add(e); Files = tmp.ToArray(); // //Find TOC int f = findTOCIndex(); if (f == -1) { return; } MemoryStream tocMemory = DecompressEntry(f, sfarStream); // //No idea what most of the rest of this stuff is so i probably shouldn't change it //Update TOC tocMemory.WriteStringASCII(inArchivePath); tocMemory.WriteByte(0xD); tocMemory.WriteByte(0xA); // //Append new FileTable int count = (int)Header.FileCount + 1; long oldsize = sfarStream.Length; long offset = oldsize; //Debug.WriteLine("File End Offset : 0x" + offset.ToString("X10")); sfarStream.Seek(oldsize, 0); Header.EntryOffset = (uint)offset; for (int i = 0; i < count; i++) { e = Files[i]; sfarStream.Write(e.Hash, 0, 16); sfarStream.Write(BitConverter.GetBytes(e.BlockSizeIndex), 0, 4); sfarStream.Write(BitConverter.GetBytes(e.UncompressedSize), 0, 4); sfarStream.WriteByte(e.UncompressedSizeAdder); sfarStream.Write(BitConverter.GetBytes(e.DataOffset), 0, 4); sfarStream.WriteByte(e.DataOffsetAdder); } offset += count * 0x1E; //Debug.WriteLine("Table End Offset : 0x" + offset.ToString("X10")); Header.BlockTableOffset = (uint)offset; // //Append blocktable for (int i = 0; i < count; i++) { e = Files[i]; if (e.BlockSizeIndex != 0xFFFFFFFF && i != f) { foreach (ushort u in e.BlockSizes) { sfarStream.Write(BitConverter.GetBytes(u), 0, 2); } } } offset = sfarStream.Length; //Debug.WriteLine("Block Table End Offset : 0x" + offset.ToString("X10")); long dataoffset = offset; sfarStream.Write(newFileBytes, 0, newFileBytes.Length); offset += newFileBytes.Length; //Debug.WriteLine("New Data End Offset : 0x" + offset.ToString("X10")); // //Append TOC long tocoffset = offset; sfarStream.Write(tocMemory.ToArray(), 0, (int)tocMemory.Length); offset = sfarStream.Length; //Debug.WriteLine("New TOC Data End Offset : 0x" + offset.ToString("X10")); //update filetable sfarStream.Seek(oldsize, 0); uint blocksizeindex = 0; for (int i = 0; i < count; i++) { e = Files[i]; sfarStream.Write(e.Hash, 0, 16); if (e.BlockSizeIndex == 0xFFFFFFFF || i == f) { sfarStream.Write(BitConverter.GetBytes(-1), 0, 4); } else { sfarStream.Write(BitConverter.GetBytes(blocksizeindex), 0, 4); e.BlockSizeIndex = blocksizeindex; blocksizeindex += (uint)e.BlockSizes.Length; Files[i] = e; } if (i == f) { sfarStream.Write(BitConverter.GetBytes(tocMemory.Length), 0, 4); sfarStream.WriteByte(0); sfarStream.Write(BitConverter.GetBytes(tocoffset), 0, 4); byte b = (byte)((tocoffset & 0xFF00000000) >> 32); sfarStream.WriteByte(b); } else if (i == count - 1) { sfarStream.Write(BitConverter.GetBytes(e.UncompressedSize), 0, 4); sfarStream.WriteByte(0); sfarStream.Write(BitConverter.GetBytes(dataoffset), 0, 4); byte b = (byte)((dataoffset & 0xFF00000000) >> 32); sfarStream.WriteByte(b); } else { sfarStream.Write(BitConverter.GetBytes(e.UncompressedSize), 0, 4); sfarStream.WriteByte(e.UncompressedSizeAdder); sfarStream.Write(BitConverter.GetBytes(e.DataOffset), 0, 4); sfarStream.WriteByte(e.DataOffsetAdder); } } //Update Header sfarStream.Seek(0xC, 0); sfarStream.Write(BitConverter.GetBytes(Header.EntryOffset), 0, 4); sfarStream.Write(BitConverter.GetBytes(count), 0, 4); sfarStream.Write(BitConverter.GetBytes(Header.BlockTableOffset), 0, 4); // sfarStream.Close(); }
private static void RandomizeDancer() { var loungeF = MERFileSystem.GetPackageFile("BioD_TwrHub_202Lounge.pcc"); if (loungeF != null && File.Exists(loungeF)) { var package = MEPackageHandler.OpenMEPackage(loungeF); var bodySM = package.GetUExport(4509); var headSM = package.GetUExport(2778); // Install new head and body assets var newInfo = DancerOptions.RandomElement(); while (newInfo.BodyAsset != null && !newInfo.BodyAsset.IsAssetFileAvailable()) { // Find another asset that is available MERLog.Information($@"Asset {newInfo.BodyAsset.AssetPath} in {newInfo.BodyAsset.PackageFile} not available, repicking..."); newInfo = DancerOptions.RandomElement(); } var newBody = PackageTools.PortExportIntoPackage(package, newInfo.BodyAsset.GetAsset()); bodySM.WriteProperty(new ObjectProperty(newBody.UIndex, "SkeletalMesh")); if (newInfo.HeadAsset != null) { var newHead = PackageTools.PortExportIntoPackage(package, newInfo.HeadAsset.GetAsset()); headSM.WriteProperty(new ObjectProperty(newHead.UIndex, "SkeletalMesh")); } else if (!newInfo.KeepHead) { headSM.RemoveProperty("SkeletalMesh"); } if (newInfo.DrawScale != 1) { // Install DS3D on the archetype. It works. Not gonna question it var ds = new CFVector3() { X = newInfo.DrawScale, Y = newInfo.DrawScale, Z = newInfo.DrawScale, }; package.GetUExport(619).WriteProperty(ds.ToLocationStructProperty("DrawScale3D")); //hack } // Install any updates to locations/rotations var dancerInstance = package.GetUExport(4510); // contains location data for dancer which may need to be slightly adjusted if (newInfo.Location != null) { dancerInstance.WriteProperty(newInfo.Location.ToLocationStructProperty("Location")); } if (newInfo.Rotation != null) { dancerInstance.WriteProperty(newInfo.Rotation.ToRotatorStructProperty("Rotation")); } if (newInfo.MorphFace != null) { var newHead = PackageTools.PortExportIntoPackage(package, newInfo.MorphFace.GetAsset()); headSM.WriteProperty(new ObjectProperty(newHead.UIndex, "MorphHead")); } MERFileSystem.SavePackage(package); } }
private void CheckModForTFCCompactability(DeploymentChecklistItem item) { // if (ModBeingDeployed.Game >= Mod.MEGame.ME2) //{ bool hasError = false; item.HasError = false; item.ItemText = M3L.GetString(M3L.string_checkingTexturesInMod); var referencedFiles = ModBeingDeployed.GetAllRelativeReferences().Select(x => Path.Combine(ModBeingDeployed.ModPath, x)).ToList(); int numChecked = 0; GameTarget validationTarget = mainWindow.InstallationTargets.FirstOrDefault(x => x.Game == ModBeingDeployed.Game); var errors = new List <string>(); foreach (var f in referencedFiles) { if (_closed) { return; } numChecked++; item.ItemText = $@"{M3L.GetString(M3L.string_checkingTexturesInMod)} [{numChecked}/{referencedFiles.Count}]"; if (f.RepresentsPackageFilePath()) { Log.Information(@"Checking file for broken textures: " + f); var package = MEPackageHandler.OpenMEPackage(f); var textures = package.Exports.Where(x => x.IsTexture()).ToList(); foreach (var texture in textures) { if (_closed) { return; } var cache = texture.GetProperty <NameProperty>(@"TextureFileCacheName"); if (cache != null) { if (!VanillaDatabaseService.IsBasegameTFCName(cache.Value, ModBeingDeployed.Game)) { var mips = Texture2D.GetTexture2DMipInfos(texture, cache.Value); Texture2D tex = new Texture2D(texture); try { tex.GetImageBytesForMip(tex.GetTopMip(), validationTarget, false); //use active target } catch (Exception e) { Log.Information(@"Found broken texture: " + texture.GetInstancedFullPath); hasError = true; item.Icon = FontAwesomeIcon.TimesCircle; item.Foreground = Brushes.Red; item.Spinning = false; errors.Add(M3L.GetString(M3L.string_interp_couldNotLoadTextureData, texture.FileRef.FilePath, texture.GetInstancedFullPath, e.Message)); } } } } } } if (!hasError) { item.Foreground = Brushes.Green; item.Icon = FontAwesomeIcon.CheckCircle; item.ItemText = M3L.GetString(M3L.string_noBrokenTexturesWereFound); item.ToolTip = M3L.GetString(M3L.string_validationOK); } else { item.Errors = errors; item.ItemText = M3L.GetString(M3L.string_textureIssuesWereDetected); item.ToolTip = M3L.GetString(M3L.string_validationFailed); } item.HasError = hasError; }
private void OpenFile_Click(object sender, RoutedEventArgs e) { OpenFileDialog d = new OpenFileDialog(); if (d.ShowDialog() == true) { string lowerFilename = System.IO.Path.GetExtension(d.FileName).ToLower(); if (d.FileName.EndsWith(".pcc") || d.FileName.EndsWith(".u") || d.FileName.EndsWith(".sfm") || d.FileName.EndsWith(".upk")) { pcc = MEPackageHandler.OpenMEPackage(d.FileName); } bytes = File.ReadAllBytes(d.FileName); Interpreter_Hexbox.ByteProvider = new DynamicByteProvider(bytes); MemoryStream inStream = new MemoryStream(bytes); UnusedSpaceList.ClearEx(); if (pcc != null) { List <UsedSpace> used = new List <UsedSpace>(); used.Add(new UsedSpace { UsedFor = "Package Header", UsedSpaceStart = 0, UsedSpaceEnd = pcc.getHeader().Length }); inStream.Seek(pcc.NameOffset, SeekOrigin.Begin); for (int i = 0; i < pcc.NameCount; i++) { int strLength = inStream.ReadValueS32(); inStream.ReadString(strLength * -2, true, Encoding.Unicode); } used.Add(new UsedSpace { UsedFor = "Name Table", UsedSpaceStart = pcc.NameOffset, UsedSpaceEnd = (int)inStream.Position }); for (int i = 0; i < pcc.ImportCount; i++) { inStream.Position += 28; } used.Add(new UsedSpace { UsedFor = "Import Table", UsedSpaceStart = pcc.ImportOffset, UsedSpaceEnd = (int)inStream.Position }); inStream.Seek(pcc.ExportOffset, SeekOrigin.Begin); for (int i = 0; i < pcc.ExportCount; i++) { inStream.Position += pcc.Exports[i].Header.Length; } used.Add(new UsedSpace { UsedFor = "Export Metadata Table", UsedSpaceStart = pcc.ExportOffset, UsedSpaceEnd = (int)inStream.Position }); used.Add(new UsedSpace { UsedFor = "Dependency Table (Unused)", UsedSpaceStart = BitConverter.ToInt32(pcc.getHeader(), 0x3A), UsedSpaceEnd = BitConverter.ToInt32(pcc.getHeader(), 0x3E) }); List <UsedSpace> usedExportsSpaces = new List <UsedSpace>(); inStream.Seek(pcc.ExportOffset, SeekOrigin.Begin); for (int i = 0; i < pcc.ExportCount; i++) { IExportEntry exp = pcc.Exports[i]; usedExportsSpaces.Add(new UsedSpace { UsedFor = $"Export {exp.UIndex}", UsedSpaceStart = exp.DataOffset, UsedSpaceEnd = exp.DataOffset + exp.DataSize }); } usedExportsSpaces = usedExportsSpaces.OrderBy(x => x.UsedSpaceStart).ToList(); List <UsedSpace> continuousBlocks = new List <UsedSpace>(); UsedSpace continuous = new UsedSpace { UsedFor = "Continuous Export Data", UsedSpaceStart = usedExportsSpaces[0].UsedSpaceStart, UsedSpaceEnd = usedExportsSpaces[0].UsedSpaceEnd }; for (int i = 1; i < usedExportsSpaces.Count; i++) { UsedSpace u = usedExportsSpaces[i]; if (continuous.UsedSpaceEnd == u.UsedSpaceStart) { continuous.UsedSpaceEnd = u.UsedSpaceEnd; } else { continuousBlocks.Add(continuous); continuous = new UsedSpace { UsedFor = "Continuous Export Data", UsedSpaceStart = u.UsedSpaceStart, UsedSpaceEnd = u.UsedSpaceEnd }; } } continuousBlocks.Add(continuous); UnusedSpaceList.AddRange(used); UnusedSpaceList.AddRange(continuousBlocks); } } }
public void ExtractFromArchive(string archivePath, string outputFolderPath, bool compressPackages, Action <string> updateTextCallback = null, Action <DetailedProgressEventArgs> extractingCallback = null, Action <string, int, int> compressedPackageCallback = null) { if (!IsInArchive) { throw new Exception(@"Cannot extract a mod that is not part of an archive."); } compressPackages &= Game == MEGame.ME3; //ME3 ONLY FOR NOW var archiveFile = archivePath.EndsWith(@".exe") ? new SevenZipExtractor(archivePath, InArchiveFormat.Nsis) : new SevenZipExtractor(archivePath); using (archiveFile) { var fileIndicesToExtract = new List <int>(); var referencedFiles = GetAllRelativeReferences(archiveFile); if (!IsVirtualized) { referencedFiles.Add(@"moddesc.ini"); } //unsure if this is required?? doesn't work for MEHEM EXE //referencedFiles = referencedFiles.Select(x => FilesystemInterposer.PathCombine(IsInArchive, ModPath, x)).ToList(); //remap to in-archive paths so they match entry paths foreach (var info in archiveFile.ArchiveFileData) { if (referencedFiles.Contains(info.FileName)) { Log.Information(@"Adding file to extraction list: " + info.FileName); fileIndicesToExtract.Add(info.Index); } } #region old /* * bool fileAdded = false; * //moddesc.ini * if (info.FileName == ModDescPath) * { * //Debug.WriteLine("Add file to extraction list: " + info.FileName); * fileIndicesToExtract.Add(info.Index); * continue; * } * * //Check each job * foreach (ModJob job in InstallationJobs) * { * if (job.Header == ModJob.JobHeader.CUSTOMDLC) * { #region Extract Custom DLC * foreach (var localCustomDLCFolder in job.CustomDLCFolderMapping.Keys) * { * if (info.FileName.StartsWith(FilesystemInterposer.PathCombine(IsInArchive, ModPath, localCustomDLCFolder))) * { * //Debug.WriteLine("Add file to extraction list: " + info.FileName); * fileIndicesToExtract.Add(info.Index); * fileAdded = true; * break; * } * } * * if (fileAdded) break; * * //Alternate files * foreach (var alt in job.AlternateFiles) * { * if (alt.AltFile != null && info.FileName.Equals(FilesystemInterposer.PathCombine(IsInArchive, ModPath, alt.AltFile), StringComparison.InvariantCultureIgnoreCase)) * { * //Debug.WriteLine("Add alternate file to extraction list: " + info.FileName); * fileIndicesToExtract.Add(info.Index); * fileAdded = true; * break; * } * } * * if (fileAdded) break; * * //Alternate DLC * foreach (var alt in job.AlternateDLCs) * { * if (info.FileName.StartsWith(FilesystemInterposer.PathCombine(IsInArchive, ModPath, alt.AlternateDLCFolder), StringComparison.InvariantCultureIgnoreCase)) * { * //Debug.WriteLine("Add alternate dlc file to extraction list: " + info.FileName); * fileIndicesToExtract.Add(info.Index); * fileAdded = true; * break; * } * } * * if (fileAdded) break; * #endregion * } * else * { #region Official headers * * foreach (var inSubDirFile in job.FilesToInstall.Values) * { * var inArchivePath = FilesystemInterposer.PathCombine(IsInArchive, ModPath, job.JobDirectory, inSubDirFile); //keep relative if unpacked mod, otherwise use full in-archive path for extraction * if (info.FileName.Equals(inArchivePath, StringComparison.InvariantCultureIgnoreCase)) * { * //Debug.WriteLine("Add file to extraction list: " + info.FileName); * fileIndicesToExtract.Add(info.Index); * fileAdded = true; * break; * } * } * * if (fileAdded) break; * //Alternate files * foreach (var alt in job.AlternateFiles) * { * if (alt.AltFile != null && info.FileName.Equals(FilesystemInterposer.PathCombine(IsInArchive, ModPath, alt.AltFile), StringComparison.InvariantCultureIgnoreCase)) * { * //Debug.WriteLine("Add alternate file to extraction list: " + info.FileName); * fileIndicesToExtract.Add(info.Index); * fileAdded = true; * break; * } * } * * if (fileAdded) break; * #endregion * } * } * }*/ #endregion archiveFile.Progressing += (sender, args) => { extractingCallback?.Invoke(args); }; string outputFilePathMapping(ArchiveFileInfo entryInfo) { string entryPath = entryInfo.FileName; if (ExeExtractionTransform != null && ExeExtractionTransform.PatchRedirects.Any(x => x.index == entryInfo.Index)) { Log.Information(@"Extracting vpatch file at index " + entryInfo.Index); return(Path.Combine(Utilities.GetVPatchRedirectsFolder(), ExeExtractionTransform.PatchRedirects.First(x => x.index == entryInfo.Index).outfile)); } if (ExeExtractionTransform != null && ExeExtractionTransform.NoExtractIndexes.Any(x => x == entryInfo.Index)) { Log.Information(@"Extracting file to trash (not used): " + entryPath); return(Path.Combine(Utilities.GetTempPath(), @"Trash", @"trashfile")); } if (ExeExtractionTransform != null && ExeExtractionTransform.AlternateRedirects.Any(x => x.index == entryInfo.Index)) { var outfile = ExeExtractionTransform.AlternateRedirects.First(x => x.index == entryInfo.Index).outfile; Log.Information($@"Extracting file with redirection: {entryPath} {outfile}"); return(Path.Combine(outputFolderPath, outfile)); } //Archive path might start with a \. Substring may return value that start with a \ var subModPath = entryPath /*.TrimStart('\\')*/.Substring(ModPath.Length).TrimStart('\\'); var path = Path.Combine(outputFolderPath, subModPath); //Debug.WriteLine("remapping output: " + entryPath + " -> " + path); return(path); } if (compressPackages) { compressionQueue = new BlockingCollection <string>(); } int numberOfPackagesToCompress = referencedFiles.Count(x => x.RepresentsPackageFilePath()); int compressedPackageCount = 0; NamedBackgroundWorker compressionThread; if (compressPackages) { compressionThread = new NamedBackgroundWorker(@"ImportingCompressionThread"); compressionThread.DoWork += (a, b) => { try { while (true) { var package = compressionQueue.Take(); //updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package))); var p = MEPackageHandler.OpenMEPackage(package); //Check if any compressed textures. bool shouldNotCompress = false; foreach (var texture in p.Exports.Where(x => x.IsTexture())) { var storageType = Texture2D.GetTopMipStorageType(texture); shouldNotCompress |= storageType == ME3Explorer.Unreal.StorageTypes.pccLZO || storageType == ME3Explorer.Unreal.StorageTypes.pccZlib; if (!shouldNotCompress) { break; } } if (!shouldNotCompress) { compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress); Log.Information(@"Compressing package: " + package); p.save(true); } else { Log.Information(@"Not compressing package due to file containing compressed textures: " + package); } Interlocked.Increment(ref compressedPackageCount); compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressedX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress); } } catch (InvalidOperationException) { //Done. lock (compressionCompletedSignaler) { Monitor.Pulse(compressionCompletedSignaler); } } }; compressionThread.RunWorkerAsync(); } archiveFile.FileExtractionFinished += (sender, args) => { if (compressPackages) { var fToCompress = outputFilePathMapping(args.FileInfo); if (fToCompress.RepresentsPackageFilePath()) { //Debug.WriteLine("Adding to blocking queue"); compressionQueue.TryAdd(fToCompress); } } }; archiveFile.ExtractFiles(outputFolderPath, outputFilePathMapping, fileIndicesToExtract.ToArray()); Log.Information(@"File extraction completed."); compressionQueue?.CompleteAdding(); if (compressPackages && numberOfPackagesToCompress > 0 && numberOfPackagesToCompress > compressedPackageCount) { Log.Information(@"Waiting for compression of packages to complete."); while (!compressionQueue.IsCompleted) { lock (compressionCompletedSignaler) { Monitor.Wait(compressionCompletedSignaler); } } Log.Information(@"Package compression has completed."); } ModPath = outputFolderPath; if (IsVirtualized) { var parser = new IniDataParser().Parse(VirtualizedIniText); parser[@"ModInfo"][@"modver"] = ModVersionString; //In event relay service resolved this File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), parser.ToString()); IsVirtualized = false; //no longer virtualized } if (ExeExtractionTransform != null) { var vpat = Utilities.GetCachedExecutablePath(@"vpat.exe"); Utilities.ExtractInternalFile(@"MassEffectModManagerCore.modmanager.executables.vpat.exe", vpat, true); //Handle VPatching foreach (var transform in ExeExtractionTransform.VPatches) { var patchfile = Path.Combine(Utilities.GetVPatchRedirectsFolder(), transform.patchfile); var inputfile = Path.Combine(ModPath, transform.inputfile); var outputfile = Path.Combine(ModPath, transform.outputfile); var args = $"\"{patchfile}\" \"{inputfile}\" \"{outputfile}\""; //do not localize Directory.CreateDirectory(Directory.GetParent(outputfile).FullName); //ensure output directory exists as vpatch will not make one. Log.Information($@"VPatching file into alternate: {inputfile} to {outputfile}"); updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_vPatchingIntoAlternate, Path.GetFileName(inputfile))); Utilities.RunProcess(vpat, args, true, false, false, true); } //Handle copyfile foreach (var copyfile in ExeExtractionTransform.CopyFiles) { string srcfile = Path.Combine(ModPath, copyfile.inputfile); string destfile = Path.Combine(ModPath, copyfile.outputfile); Log.Information($@"Applying transform copyfile: {srcfile} -> {destfile}"); File.Copy(srcfile, destfile, true); } if (ExeExtractionTransform.PostTransformModdesc != null) { //fetch online moddesc for this mod. Log.Information(@"Fetching post-transform third party moddesc."); var moddesc = OnlineContent.FetchThirdPartyModdesc(ExeExtractionTransform.PostTransformModdesc); File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), moddesc); } } //int packagesCompressed = 0; //if (compressPackages) //{ // var packages = Utilities.GetPackagesInDirectory(ModPath, true); // extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0)); // foreach (var package in packages) // { // updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package))); // Log.Information("Compressing package: " + package); // var p = MEPackageHandler.OpenMEPackage(package); // p.save(true); // packagesCompressed++; // extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0)); // } //} } }
/// <summary> /// Checks mods for updates. ForceUpdateCheck will force the mod to validate against the server (essentially repair mode). It is not used for rate limiting! /// </summary> /// <param name="modsToCheck">Mods to have server send information about</param> /// <param name="forceUpdateCheck">Force update check regardless of version</param> /// <returns></returns> public static List <ModUpdateInfo> CheckForModUpdates(List <Mod> modsToCheck, bool forceUpdateCheck, Action <string> updateStatusCallback = null) { string updateFinalRequest = UpdaterServiceManifestEndpoint; bool first = true; foreach (var mod in modsToCheck) { if (mod.ModModMakerID > 0) { //Modmaker style if (first) { updateFinalRequest += "?"; first = false; } else { updateFinalRequest += "&"; } updateFinalRequest += "modmakerupdatecode[]=" + mod.ModModMakerID; } else if (mod.ModClassicUpdateCode > 0) { //Classic style if (first) { updateFinalRequest += "?"; first = false; } else { updateFinalRequest += "&"; } updateFinalRequest += "classicupdatecode[]=" + mod.ModClassicUpdateCode; } else if (mod.NexusModID > 0 && mod.NexusUpdateCheck) { //Nexus style if (first) { updateFinalRequest += "?"; first = false; } else { updateFinalRequest += "&"; } updateFinalRequest += "nexusupdatecode[]=" + mod.Game.ToString().Substring(2) + "-" + mod.NexusModID; } //else if (mod.NexusModID > 0) //{ // //Classic style // if (first) // { // updateFinalRequest += "?"; // first = false; // } // else // { // updateFinalRequest += "&"; // } // updateFinalRequest += "nexusupdatecode[]=" + mod.ModClassicUpdateCode; //} } using var wc = new System.Net.WebClient(); try { Debug.WriteLine(updateFinalRequest); string updatexml = wc.DownloadStringAwareOfEncoding(updateFinalRequest); XElement rootElement = XElement.Parse(updatexml); #region classic mods var modUpdateInfos = new List <ModUpdateInfo>(); var classicUpdateInfos = (from e in rootElement.Elements("mod") select new ModUpdateInfo { changelog = (string)e.Attribute("changelog"), versionstr = (string)e.Attribute("version"), updatecode = (int)e.Attribute("updatecode"), serverfolder = (string)e.Attribute("folder"), sourceFiles = (from f in e.Elements("sourcefile") select new SourceFile { lzmahash = (string)f.Attribute("lzmahash"), hash = (string)f.Attribute("hash"), size = (int)f.Attribute("size"), lzmasize = (int)f.Attribute("lzmasize"), relativefilepath = f.Value, timestamp = (Int64?)f.Attribute("timestamp") ?? (Int64)0 }).ToList(), blacklistedFiles = e.Elements("blacklistedfile").Select(x => x.Value).ToList() }).ToList(); // CALCULATE UPDATE DELTA CaseInsensitiveDictionary <USFileInfo> hashMap = new CaseInsensitiveDictionary <USFileInfo>(); //Used to rename files foreach (var modUpdateInfo in classicUpdateInfos) { modUpdateInfo.ResolveVersionVar(); //Calculate update information var matchingMod = modsToCheck.FirstOrDefault(x => x.ModClassicUpdateCode == modUpdateInfo.updatecode); if (matchingMod != null && (forceUpdateCheck || matchingMod.ParsedModVersion < modUpdateInfo.version)) { // The following line is left so we know that it was at one point considered implemented. // This prevents updating copies of the same mod in the library. Cause it's just kind of a bandwidth waste. //modsToCheck.Remove(matchingMod); //This makes it so we don't feed in multiple same-mods. For example, nexus check on 3 Project Variety downloads modUpdateInfo.mod = matchingMod; modUpdateInfo.SetLocalizedInfo(); string modBasepath = matchingMod.ModPath; double i = 0; List <string> references = null; try { references = matchingMod.GetAllRelativeReferences(true); } catch { // There's an error. Underlying disk state may have changed since we originally loaded the mod } if (references == null || !matchingMod.ValidMod) { // The mod failed to load. We should just index everything the // references will not be dfully parsed. var localFiles = Directory.GetFiles(matchingMod.ModPath, "*", SearchOption.AllDirectories); references = localFiles.Select(x => x.Substring(matchingMod.ModPath.Length + 1)).ToList(); } int total = references.Count; // Index existing files foreach (var v in references) { updateStatusCallback?.Invoke( $"Indexing {modUpdateInfo.mod.ModName} for updates {(int)(i * 100 / total)}%"); i++; var fpath = Path.Combine(matchingMod.ModPath, v); if (fpath.RepresentsPackageFilePath()) { // We need to make sure it's decompressed var qPackage = MEPackageHandler.QuickOpenMEPackage(fpath); if (qPackage.IsCompressed) { CLog.Information( $" >> Decompressing compressed package for update comparison check: {fpath}", Settings.LogModUpdater); qPackage = MEPackageHandler.OpenMEPackage(fpath); MemoryStream tStream = new MemoryStream(); tStream = qPackage.SaveToStream(false); hashMap[v] = new USFileInfo() { MD5 = Utilities.CalculateMD5(tStream), CompressedMD5 = Utilities.CalculateMD5(fpath), Filesize = tStream.Length, RelativeFilepath = v }; continue; } } hashMap[v] = new USFileInfo() { MD5 = Utilities.CalculateMD5(fpath), Filesize = new FileInfo(fpath).Length, RelativeFilepath = v }; } i = 0; total = modUpdateInfo.sourceFiles.Count; foreach (var serverFile in modUpdateInfo.sourceFiles) { Log.Information($@"Checking {serverFile.relativefilepath} for update applicability"); updateStatusCallback?.Invoke( $"Calculating update delta for {modUpdateInfo.mod.ModName} {(int)(i * 100 / total)}%"); i++; bool calculatedOp = false; if (hashMap.TryGetValue(serverFile.relativefilepath, out var indexInfo)) { if (indexInfo.MD5 == serverFile.hash) { CLog.Information(@" >> File is up to date", Settings.LogModUpdater); calculatedOp = true; } else if (indexInfo.CompressedMD5 != null && indexInfo.CompressedMD5 == serverFile.hash) { CLog.Information(@" >> Compressed package file is up to date", Settings.LogModUpdater); calculatedOp = true; } } if (!calculatedOp) { // File is missing or hash was wrong. We should try to map it to another existing file // to save bandwidth var existingFilesThatMatchServerHash = hashMap.Where(x => x.Value.MD5 == serverFile.hash || (x.Value.CompressedMD5 != null && x.Value.CompressedMD5 == serverFile.hash)) .ToList(); if (existingFilesThatMatchServerHash.Any()) { CLog.Information( $" >> Server file can be cloned from local file {existingFilesThatMatchServerHash[0].Value.RelativeFilepath} as it has same hash", Settings.LogModUpdater); modUpdateInfo.cloneOperations[serverFile] = existingFilesThatMatchServerHash[0] .Value; // Server file can be sourced from the value } else if (indexInfo == null) { // we don't have file hashed (new file) CLog.Information( $" >> Applicable for updates, File does not exist locally", Settings.LogModUpdater); modUpdateInfo.applicableUpdates.Add(serverFile); } else { // Existing file has wrong hash CLog.Information($" >> Applicable for updates, hash has changed", Settings.LogModUpdater); modUpdateInfo.applicableUpdates.Add(serverFile); } } } foreach (var blacklistedFile in modUpdateInfo.blacklistedFiles) { var blLocalFile = Path.Combine(modBasepath, blacklistedFile); if (File.Exists(blLocalFile)) { Log.Information(@"Blacklisted file marked for deletion: " + blLocalFile); modUpdateInfo.filesToDelete.Add(blLocalFile); } } // alphabetize files modUpdateInfo.applicableUpdates.Sort(x => x.relativefilepath); //Files to remove calculation var modFiles = Directory.GetFiles(modBasepath, "*", SearchOption.AllDirectories) .Select(x => x.Substring(modBasepath.Length + 1)).ToList(); var additionalFilesToDelete = modFiles.Except( modUpdateInfo.sourceFiles.Select(x => x.relativefilepath), StringComparer.InvariantCultureIgnoreCase).Distinct().ToList(); modUpdateInfo.filesToDelete.AddRange( additionalFilesToDelete); //Todo: Add security check here to prevent malicious modUpdateInfo.TotalBytesToDownload = modUpdateInfo.applicableUpdates.Sum(x => x.lzmasize); } } modUpdateInfos.AddRange(classicUpdateInfos); #endregion #region modmaker mods var modmakerModUpdateInfos = (from e in rootElement.Elements("modmakermod") select new ModMakerModUpdateInfo { ModMakerId = (int)e.Attribute("id"), versionstr = (string)e.Attribute("version"), PublishDate = DateTime.ParseExact((string)e.Attribute("publishdate"), "yyyy-MM-dd", CultureInfo.InvariantCulture), changelog = (string)e.Attribute("changelog") }).ToList(); modUpdateInfos.AddRange(modmakerModUpdateInfos); #endregion #region Nexus Mod Third Party var nexusModsUpdateInfo = (from e in rootElement.Elements("nexusmod") select new NexusModUpdateInfo { NexusModsId = (int)e.Attribute("id"), GameId = (int)e.Attribute("game"), versionstr = (string)e.Attribute("version"), UpdatedTime = DateTimeOffset.FromUnixTimeSeconds((long)e.Attribute("updated_timestamp")) .DateTime }).ToList(); modUpdateInfos.AddRange(nexusModsUpdateInfo); #endregion return(modUpdateInfos); } catch (Exception e) { Log.Error("Error checking for mod updates: " + App.FlattenException(e)); Crashes.TrackError(e, new Dictionary <string, string>() { { "Update check URL", updateFinalRequest } }); } return(null); }
private static void RandomizeArtGallery() { var wideAssets = TFCBuilder.ListTextureAssets("Kasumi.ArtGallery.wide").Select(x => $"Kasumi.ArtGallery.wide.{x}").ToList(); var tallAssets = TFCBuilder.ListTextureAssets("Kasumi.ArtGallery.tall").Select(x => $"Kasumi.ArtGallery.tall.{x}").ToList(); wideAssets.Shuffle(); tallAssets.Shuffle(); var artyGallery = GetKasumiArtGallerySetup(); foreach (var kagf in artyGallery) { var artGalleryF = MERFileSystem.GetPackageFile(kagf.PackageName); if (artGalleryF != null && File.Exists(artGalleryF)) { var artGalleryP = MEPackageHandler.OpenMEPackage(artGalleryF); // Rename instances so they're memory unique so we have a few more paintings if (kagf.RenameMemoryInstances) { if (kagf.TallTextureUIndexes != null) { foreach (var uindex in kagf.TallTextureUIndexes) { if (!artGalleryP.GetUExport(uindex).IsTexture()) { Debugger.Break(); } artGalleryP.GetUExport(uindex).ObjectName = $"ME2R_T_KASUMIPAINTING{ThreadSafeRandom.Next(15000)}"; } } // Rename mats so they're also unique if (kagf.WideTextureUIndexes != null) { foreach (var uindex in kagf.WideTextureUIndexes) { if (!artGalleryP.GetUExport(uindex).IsTexture()) { Debugger.Break(); } artGalleryP.GetUExport(uindex).ObjectName = $"ME2R_W_KASUMIPAINTING{ThreadSafeRandom.Next(15000)}"; } } if (kagf.MaterialUIndexes != null) { foreach (var uindex in kagf.MaterialUIndexes) { var exp = artGalleryP.GetUExport(uindex); if (!exp.ClassName.Contains("Material")) { Debugger.Break(); } artGalleryP.GetUExport(uindex).ObjectName = $"ME2R_PAINTMAT_KASUMI{ThreadSafeRandom.Next(15000)}"; } } } InstallARArtTextures(kagf.WideTextureUIndexes, wideAssets, artGalleryP, "Wide"); InstallARArtTextures(kagf.TallTextureUIndexes, tallAssets, artGalleryP, "Tall"); MERFileSystem.SavePackage(artGalleryP); } } }
public static bool RandomizeGalaxyMap(RandomizationOption option) { // Make the ship faster because otherwise it takes ages to do stuff // And can also consume more fuel var sfxgame = MERFileSystem.GetPackageFile("SFXGame.pcc"); if (sfxgame != null && File.Exists(sfxgame)) { var sfxgameP = MEPackageHandler.OpenMEPackage(sfxgame); var galaxyModCamDefaults = sfxgameP.GetUExport(3899); var props = galaxyModCamDefaults.GetProperties(); props.AddOrReplaceProp(new FloatProperty(150, "m_fMovementScalarGalaxy")); // is this used? props.AddOrReplaceProp(new FloatProperty(75, "m_fMovementScalarCluster")); props.AddOrReplaceProp(new FloatProperty(125, "m_fMovementScalarSystem")); galaxyModCamDefaults.WriteProperties(props); // Make it so you can't run out of a gas. if (option.HasSubOptionSelected(SUBOPTIONKEY_INFINITEGAS)) { var BurnFuel = sfxgameP.GetUExport(3877); if (BurnFuel.ObjectName == "BurnFuel") { var bfData = BurnFuel.Data; bfData.OverwriteRange(0x9C, BitConverter.GetBytes(50f)); // Make it so we don't run out of gas BurnFuel.Data = bfData; } } MERFileSystem.SavePackage(sfxgameP); } // Give a bit more starting gas // IDK why it's in Weapon // This doesn't seem to actually do anything. But i'll leave it here anyways var weaponini = CoalescedHandler.GetIniFile("BIOWeapon.ini"); var sfxinvmgr = weaponini.GetOrAddSection("SFXGame.SFXInventoryManager"); sfxinvmgr.SetSingleEntry("FuelEfficiency", 5); // Make faster deceleration cause its hard to stop right var biogameini = CoalescedHandler.GetIniFile("BIOGame.ini"); var camgalaxy = biogameini.GetOrAddSection("SFXGame.BioCameraBehaviorGalaxy"); camgalaxy.SetSingleEntry("m_fShipSystemDeccel", 25); camgalaxy.SetSingleEntry("m_fShipClusterDeccel", .7f); var packageF = MERFileSystem.GetPackageFile(@"BioD_Nor_103aGalaxyMap.pcc"); var package = MEPackageHandler.OpenMEPackage(packageF); foreach (ExportEntry export in package.Exports) { switch (export.ClassName) { case "SFXCluster": { var props = export.GetProperties(); var starColor = props.GetProp <StructProperty>("StarColor"); if (starColor != null) { RStructs.RandomizeTint(starColor, false); } starColor = props.GetProp <StructProperty>("StarColor2"); if (starColor != null) { RStructs.RandomizeTint(starColor, false); } props.GetProp <IntProperty>("PosX").Value = ThreadSafeRandom.Next(800); props.GetProp <IntProperty>("PosY").Value = ThreadSafeRandom.Next(800); var intensity = props.GetProp <FloatProperty>("SphereIntensity"); if (intensity != null) { intensity.Value = ThreadSafeRandom.NextFloat(0, 6); } intensity = props.GetProp <FloatProperty>("NebularDensity"); if (intensity != null) { intensity.Value = ThreadSafeRandom.NextFloat(0, 6); } intensity = props.GetProp <FloatProperty>("SphereSize"); if (intensity != null) { intensity.Value = ThreadSafeRandom.NextFloat(0, 6); } export.WriteProperties(props); } //RandomizeClustersXY(export); break; case "SFXSystem": { var props = export.GetProperties(); var starColor = props.GetProp <StructProperty>("StarColor"); if (starColor != null) { RStructs.RandomizeTint(starColor, false); } starColor = props.GetProp <StructProperty>("FlareTint"); if (starColor != null) { RStructs.RandomizeTint(starColor, false); } starColor = props.GetProp <StructProperty>("SunColor"); if (starColor != null) { RStructs.RandomizeTint(starColor, false); } props.GetProp <IntProperty>("PosX").Value = ThreadSafeRandom.Next(1000); props.GetProp <IntProperty>("PosY").Value = ThreadSafeRandom.Next(1000); var scale = props.GetProp <FloatProperty>("Scale"); if (scale != null) { scale.Value = ThreadSafeRandom.NextFloat(.1, 2); } export.WriteProperties(props); } break; case "BioPlanet": { var props = export.GetProperties(); var starColor = props.GetProp <StructProperty>("SunColor"); if (starColor != null) { RStructs.RandomizeTint(starColor, false); } starColor = props.GetProp <StructProperty>("FlareTint"); if (starColor != null) { RStructs.RandomizeTint(starColor, false); } starColor = props.GetProp <StructProperty>("CloudColor"); if (starColor != null) { RStructs.RandomizeTint(starColor, false); } var resourceRichness = props.GetProp <FloatProperty>("ResourceRichness"); if (resourceRichness != null) { resourceRichness.Value = ThreadSafeRandom.NextFloat(0, 1.2); } else { props.AddOrReplaceProp(new FloatProperty(ThreadSafeRandom.NextFloat(0, .6), "ResourceRichness")); } props.GetProp <IntProperty>("PosX").Value = ThreadSafeRandom.Next(1000); props.GetProp <IntProperty>("PosY").Value = ThreadSafeRandom.Next(1000); var scale = props.GetProp <FloatProperty>("Scale"); if (scale != null) { scale.Value = ThreadSafeRandom.NextFloat(.1, 6); } export.WriteProperties(props); } break; case "MaterialInstanceConstant": RMaterialInstance.RandomizeExport(export, null); break; } } MERFileSystem.SavePackage(package); return(true); }
private void StartGuiCompatibilityScanner() { NamedBackgroundWorker bw = new NamedBackgroundWorker(@"GUICompatibilityScanner"); bw.DoWork += (a, b) => { Percent = 0; ActionString = M3L.GetString(M3L.string_preparingCompatGenerator); ActionSubstring = M3L.GetString(M3L.string_pleaseWait); var installedDLCMods = VanillaDatabaseService.GetInstalledDLCMods(target); var numTotalDLCMods = installedDLCMods.Count; var uiModInstalled = installedDLCMods.Intersect(DLCUIModFolderNames).Any(); var dlcRoot = MEDirectories.DLCPath(target); if (uiModInstalled) { var nonUIinstalledDLCMods = installedDLCMods.Except(DLCUIModFolderNamesIncludingPatch).ToList(); if (nonUIinstalledDLCMods.Count < numTotalDLCMods && nonUIinstalledDLCMods.Count > 0) { //Get UI library bool xbxLibrary = installedDLCMods.Contains(@"DLC_CON_XBX"); bool uiscalinglibrary = installedDLCMods.Contains(@"DLC_CON_UIScaling"); if (!xbxLibrary && !uiscalinglibrary) { uiscalinglibrary = installedDLCMods.Contains(@"DLC_CON_UIScaling_Shared"); } if (xbxLibrary && uiscalinglibrary) { //can't have both! Not supported. Application.Current.Dispatcher.Invoke(delegate { Log.Error(@"Cannot make compat pack: Both ISM and SP Controller are installed, this is not supported."); M3L.ShowDialog(window, M3L.GetString(M3L.string_dialogCannotGenerateCompatPackInvalidConfig), M3L.GetString(M3L.string_invalidConfiguration), MessageBoxButton.OK, MessageBoxImage.Error); OnClosing(DataEventArgs.Empty); }); b.Result = GUICompatibilityThreadResult.INVALID_UI_MOD_CONFIG; return; } void progressCallback(long done, long total) { ActionString = M3L.GetString(M3L.string_downloadingUiLibrary); ActionSubstring = xbxLibrary ? @"DLC_CON_XBX" : @"DLC_CON_UIScaling"; Percent = getPercent(done, total); } var uiLibraryPath = GetUILibraryPath(xbxLibrary ? @"DLC_CON_XBX" : @"DLC_CON_UIScaling", true, progressCallback); if (uiLibraryPath == null) { Log.Error(@"Required UI library could not be downloaded."); Application.Current.Dispatcher.Invoke(delegate { M3L.ShowDialog(window, M3L.GetString(M3L.string_cannotGeneratorCompatPackCouldNotDownload), M3L.GetString(M3L.string_couldNotAcquireUiLibrary), MessageBoxButton.OK, MessageBoxImage.Error); OnClosing(DataEventArgs.Empty); }); b.Result = GUICompatibilityThreadResult.NO_UI_LIBRARY; return; } //Open UI library SevenZipExtractor libraryArchive = new SevenZipExtractor(uiLibraryPath); List <string> libraryGUIs = libraryArchive.ArchiveFileData.Where(x => !x.IsDirectory).Select(x => x.FileName.Substring(Path.GetFileNameWithoutExtension(uiLibraryPath).Length + 1)).Select(x => x.Substring(0, x.Length - 4)).ToList(); //remove / on end too //We have UI mod(s) installed and at least one other DLC mod. var supercedanceList = getFileSupercedances().Where(x => x.Value.Any(x => !DLCUIModFolderNamesIncludingPatch.Contains(x))).ToDictionary(p => p.Key, p => p.Value); //Find GUIs ConcurrentDictionary <string, string> filesToBePatched = new ConcurrentDictionary <string, string>(); //Dictionary because there is no ConcurrentList. Keys and values are idenitcal. ActionString = M3L.GetString(M3L.string_scanningForGuiExports); ActionSubstring = M3L.GetString(M3L.string_pleaseWait); Percent = 0; int done = 0; string singlesuffix = M3L.GetString(M3L.string_singularFile); string pluralsuffix = M3L.GetString(M3L.string_pluralFiles); Parallel.ForEach(supercedanceList, new ParallelOptions() { MaxDegreeOfParallelism = 4 }, (pair) => { var firstNonUIModDlc = pair.Value.FirstOrDefault(x => !DLCUIModFolderNamesIncludingPatch.Contains(x)); if (firstNonUIModDlc != null) { //Scan file. var packagefile = Path.Combine(dlcRoot, firstNonUIModDlc, target.Game == MEGame.ME3 ? @"CookedPCConsole" : @"CookedPC", pair.Key); Log.Information(@"Scanning file for GFXMovieInfo exports: " + packagefile); if (!File.Exists(packagefile)) { throw new Exception($@"Package file for inspecting GUIs in was not found: {packagefile}"); } var package = MEPackageHandler.OpenMEPackage(packagefile); var guiExports = package.Exports.Where(x => !x.IsDefaultObject && x.ClassName == @"GFxMovieInfo").ToList(); if (guiExports.Count > 0) { //potential item needing replacement //Check GUI library to see if we have anything. foreach (var export in guiExports) { if (libraryGUIs.Contains(export.GetFullPath, StringComparer.InvariantCultureIgnoreCase)) { //match filesToBePatched[packagefile] = packagefile; ActionSubstring = M3L.GetString(M3L.string_interp_XFilesNeedToBePatched, filesToBePatched.Count.ToString(), filesToBePatched.Count == 1 ? singlesuffix : pluralsuffix); Log.Information($@"{firstNonUIModDlc} {pair.Key} has GUI export that is in UI library, marking for patching. Trigger: {export.GetFullPath}"); break; } } } } Interlocked.Increment(ref done); Percent = getPercent(done, supercedanceList.Count); }); if (filesToBePatched.Count > 0) { Log.Information(@"A GUI compatibility patch is required for this game configuration"); b.Result = GUICompatibilityThreadResult.REQUIRED; var generatedMod = GenerateCompatibilityPackForFiles(nonUIinstalledDLCMods, filesToBePatched.Keys.ToList(), libraryArchive); b.Result = GUICompatibilityThreadResult.GENERATED_PACK; Application.Current.Dispatcher.Invoke(delegate { ((MainWindow)window).LoadMods(generatedMod); }); //reload to this mod } } Log.Information(@"A GUI compatibility patch is not required for this game configuration"); b.Result = GUICompatibilityThreadResult.NOT_REQUIRED; } else { Log.Information(@"No UI mods are installed - no GUI compatibility pack required"); b.Result = GUICompatibilityThreadResult.NO_UI_MODS_INSTALLED; } }; bw.RunWorkerCompleted += (a, b) => { if (b.Result is GUICompatibilityThreadResult gctr) { Analytics.TrackEvent(@"Generated a UI compatibility pack", new Dictionary <string, string>() { { @"Result", gctr.ToString() } }); OnClosing(DataEventArgs.Empty); } else { throw new Exception(@"GUI Compatibility generator thread did not return a result! Please report this to ME3Tweaks"); } }; bw.RunWorkerAsync(); }
public void CreateLiveEditFile() { string filePath = LiveFaceFxEditorFilePath; File.Copy(Path.Combine(App.ExecFolder, "ME3EmptyLevel.pcc"), filePath); LiveFile = MEPackageHandler.OpenMEPackage(filePath); for (int i = 0; i < LiveFile.Names.Count; i++) { if (LiveFile.Names[i].Equals("ME3EmptyLevel")) { LiveFile.replaceName(i, Path.GetFileNameWithoutExtension(filePath)); } } var packguid = Guid.NewGuid(); var package = LiveFile.GetUExport(1); package.PackageGUID = packguid; LiveFile.PackageGuid = packguid; EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, SourceAnimSet.Export.Parent, LiveFile, null, true, out _); var gender = SourceAnimSet.Export.ObjectNameString.Last() switch { 'F' => FaceFXGender.Female, 'M' => FaceFXGender.Male, _ => FaceFXGender.NonSpkr }; ActorTag = null; try { if (gender is not FaceFXGender.NonSpkr) { bool isFemale = gender is FaceFXGender.Female; var bioConv = LiveFile.Exports.First(exp => exp.Class.ObjectName == "BioConversation"); var LiveFileAnimSet = LiveFile.FindExport(SourceAnimSet.Export.InstancedFullPath); var propName = isFemale ? "m_aMaleFaceSets" : "m_aFemaleFaceSets"; int idx = bioConv.GetProperty <ArrayProperty <ObjectProperty> >(propName).FindIndex(objProp => objProp.Value == LiveFileAnimSet.UIndex); if (idx is 0) { //player ActorTag = $"Player_{(isFemale ? 'F' : 'M')}"; IEntry ent; using (IMEPackage soldierFile = MEPackageHandler.OpenME3Package(Path.Combine(ME3Directory.CookedPCPath, "SFXCharacterClass_Soldier.pcc"))) { EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, soldierFile.GetUExport(isFemale ? 10327 : 10330), LiveFile, LiveFile.GetUExport(3), true, out ent); } ExportEntry actor = (ExportEntry)ent; LiveFile.AddToLevelActorsIfNotThere(actor); actor.WriteProperty(new NameProperty(ActorTag, "Tag")); using (IMEPackage clothingFile = MEPackageHandler.OpenME3Package(Path.Combine(ME3Directory.CookedPCPath, $"BIOG_HM{(isFemale ? "F" : "M")}_ARM_CTH_R.pcc"))) { var clothingPackage = EntryImporter.GetOrAddCrossImportOrPackage("CTHa", clothingFile, LiveFile); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, clothingFile.GetUExport(isFemale ? 1625 : 1966), LiveFile, clothingPackage, true, out ent); } ExportEntry bodyComponent = LiveFile.GetUExport(actor.GetProperty <ObjectProperty>("BodyMesh").Value); bodyComponent.WriteProperty(new ObjectProperty(ent.UIndex, "SkeletalMesh")); } else if (idx is 1) { //owner using IMEPackage parentFile = getParentFile(); foreach (ExportEntry export in parentFile.Exports.Where(exp => exp.ClassName is "SFXSeqAct_StartConversation" or "SFXSeqAct_StartAmbientConv")) { if (export.GetProperty <ObjectProperty>("Conv") is ObjectProperty convProp && parentFile.TryGetImport(convProp.Value, out var convImport) && convImport.ObjectName == bioConv.ObjectName) { ExportEntry seqVar = parentFile.GetUExport(export.GetProperty <ArrayProperty <StructProperty> >("VariableLinks")[0].GetProp <ArrayProperty <ObjectProperty> >("LinkedVariables")[0].Value); if (seqVar.ClassName == "BioSeqVar_ObjectFindByTag") { ActorTag = seqVar.GetProperty <NameProperty>("m_sObjectTagToFind").Value; if (!ActorTag.StartsWith("hench_", StringComparison.OrdinalIgnoreCase)) { importTaggedActor(parentFile); } } else { EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, parentFile.GetUExport(seqVar.GetProperty <ObjectProperty>("ObjValue").Value), LiveFile, LiveFile.GetUExport(3), true, out var ent); ExportEntry actor = (ExportEntry)ent; LiveFile.AddToLevelActorsIfNotThere(actor); ActorTag = actor.GetProperty <NameProperty>("Tag")?.Value.Name; if (ActorTag is null) { ActorTag = "ConvoOwner"; actor.WriteProperty(new NameProperty(ActorTag, "Tag")); } } break; } } } else { ActorTag = bioConv.GetProperty <ArrayProperty <NameProperty> >("m_aSpeakerList")[idx - 2].Value; if (!ActorTag.StartsWith("hench_", StringComparison.OrdinalIgnoreCase)) { using IMEPackage parentFile = getParentFile(); importTaggedActor(parentFile); } } } else { //find nonspkr linkage somehow } }