Exemple #1
0
        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);
        }
Exemple #2
0
        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);
        }
Exemple #4
0
        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();
        }
Exemple #5
0
 public void LoadMEPackage(string s)
 {
     Pcc?.Release(winForm: this);
     Pcc = MEPackageHandler.OpenMEPackage(s, winForm: this);
 }
Exemple #6
0
        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();
        }
Exemple #7
0
 public MeshExporter(string pccPath)
 {
     MEPackageHandler.Initialize();
     Pcc = MEPackageHandler.OpenMEPackage(pccPath);
 }
Exemple #8
0
        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!");
                        }
                    }
                }
            }
        }
Exemple #9
0
        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");
                    }
                }
            }
        }
Exemple #10
0
        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);
                }
            }
        }
Exemple #11
0
        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);
        }
Exemple #13
0
        /// <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);
                                            }
                                        }
Exemple #14
0
        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);
            }
        }
Exemple #15
0
        /// <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();
            }
        }
Exemple #16
0
 public void LoadMEPackage(string s)
 {
     pcc?.Release(wpfWindow: this);
     pcc = MEPackageHandler.OpenMEPackage(s, wpfWindow: this);
 }
Exemple #17
0
        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);
            }
        }
Exemple #19
0
        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);
        }
Exemple #21
0
        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);
            }
        }
Exemple #23
0
        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);
        }
Exemple #27
0
        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);
        }
Exemple #29
0
        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
                }
            }