Example #1
0
        private static Mod AttemptLoadVirtualMod(ArchiveFileInfo sfarEntry, SevenZipExtractor archive, Mod.MEGame game, string md5)
        {
            var sfarPath   = sfarEntry.FileName;
            var cookedPath = FilesystemInterposer.DirectoryGetParent(sfarPath, true);

            //Todo: Check if value is CookedPC/CookedPCConsole as further validation
            if (!string.IsNullOrEmpty(FilesystemInterposer.DirectoryGetParent(cookedPath, true)))
            {
                var dlcDir        = FilesystemInterposer.DirectoryGetParent(cookedPath, true);
                var dlcFolderName = Path.GetFileName(dlcDir);
                if (!string.IsNullOrEmpty(dlcFolderName))
                {
                    var thirdPartyInfo = ThirdPartyServices.GetThirdPartyModInfo(dlcFolderName, game);
                    if (thirdPartyInfo != null)
                    {
                        Log.Information($@"Third party mod found: {thirdPartyInfo.modname}, preparing virtual moddesc.ini");
                        //We will have to load a virtual moddesc. Since Mod constructor requires reading an ini, we will build and feed it a virtual one.
                        IniData virtualModDesc = new IniData();
                        virtualModDesc[@"ModManager"][@"cmmver"]     = App.HighestSupportedModDesc.ToString();
                        virtualModDesc[@"ModManager"][@"importedby"] = App.BuildNumber.ToString();
                        virtualModDesc[@"ModInfo"][@"game"]          = @"ME3";
                        virtualModDesc[@"ModInfo"][@"modname"]       = thirdPartyInfo.modname;
                        virtualModDesc[@"ModInfo"][@"moddev"]        = thirdPartyInfo.moddev;
                        virtualModDesc[@"ModInfo"][@"modsite"]       = thirdPartyInfo.modsite;
                        virtualModDesc[@"ModInfo"][@"moddesc"]       = thirdPartyInfo.moddesc;
                        virtualModDesc[@"ModInfo"][@"unofficial"]    = @"true";
                        if (int.TryParse(thirdPartyInfo.updatecode, out var updatecode) && updatecode > 0)
                        {
                            virtualModDesc[@"ModInfo"][@"updatecode"] = updatecode.ToString();
                            virtualModDesc[@"ModInfo"][@"modver"]     = 0.001.ToString(CultureInfo.InvariantCulture); //This will force mod to check for update after reload
                        }
                        else
                        {
                            virtualModDesc[@"ModInfo"][@"modver"] = 0.0.ToString(CultureInfo.InvariantCulture); //Will attempt to look up later after mods have parsed.
                        }

                        virtualModDesc[@"CUSTOMDLC"][@"sourcedirs"]        = dlcFolderName;
                        virtualModDesc[@"CUSTOMDLC"][@"destdirs"]          = dlcFolderName;
                        virtualModDesc[@"UPDATES"][@"originalarchivehash"] = md5;

                        var archiveSize    = new FileInfo(archive.FileName).Length;
                        var importingInfos = ThirdPartyServices.GetImportingInfosBySize(archiveSize);
                        if (importingInfos.Count == 1 && importingInfos[0].GetParsedRequiredDLC().Count > 0)
                        {
                            OnlineContent.QueryModRelay(importingInfos[0].md5, archiveSize); //Tell telemetry relay we are accessing the TPIS for an existing item so it can update latest for tracking
                            virtualModDesc[@"ModInfo"][@"requireddlc"] = importingInfos[0].requireddlc;
                        }

                        return(new Mod(virtualModDesc.ToString(), FilesystemInterposer.DirectoryGetParent(dlcDir, true), archive));
                    }
                }
                else
                {
                    Log.Information($@"No third party mod information for importing {dlcFolderName}. Should this be supported for import? Contact Mgamerz on the ME3Tweaks Discord if it should.");
                }
            }

            return(null);
        }
Example #2
0
 public void OnSelectedTargetChanged()
 {
     InstalledDLCs.ClearEx();
     if (SelectedTarget != null)
     {
         // maps DLC folder name -> mount number
         var installedDlc = VanillaDatabaseService.GetInstalledOfficialDLC(SelectedTarget, true);
         foreach (var dlc in installedDlc)
         {
             Debug.WriteLine(dlc);
             var foldername = dlc.TrimStart('x');
             InstalledDLCs.Add(new InstalledDLC()
             {
                 target          = SelectedTarget,
                 DLCFolderName   = dlc,
                 UIDLCFolderName = foldername,
                 Enabled         = !dlc.StartsWith('x'),
                 HumanName       = ThirdPartyServices.GetThirdPartyModInfo(foldername, SelectedTarget.Game).modname
             });
         }
     }
 }
Example #3
0
        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);

            ActionSubstring = M3L.GetString(M3L.string_interp_patchedXY, done.ToString(), done == 1 ? singlesuffix : pluralsuffix);
            foreach (var file in filesToBePatched)
            {
                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);
        }
Example #4
0
            private void BeginBackup()
            {
                var targetToBackup = BackupSourceTarget;

                if (!targetToBackup.IsCustomOption)
                {
                    if (Utilities.IsGameRunning(targetToBackup.Game))
                    {
                        M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_cannotBackupGameWhileRunning, Utilities.GetGameName(BackupSourceTarget.Game)), M3L.GetString(M3L.string_gameRunning), MessageBoxButton.OK, MessageBoxImage.Error);
                        return;
                    }
                }
                else
                {
                    // Point to existing game installation
                    Log.Information(@"BeginBackup() with IsCustomOption.");
                    var linkWarning = M3L.ShowDialog(window,
                                                     M3L.GetString(M3L.string_dialog_linkTargetWontBeModdable), M3L.GetString(M3L.string_linkWarning), MessageBoxButton.OKCancel, MessageBoxImage.Warning);
                    if (linkWarning == MessageBoxResult.Cancel)
                    {
                        Log.Information(@"User aborted linking due to dialog");
                        return;
                    }

                    Log.Information(@"Prompting user to select executable of link target");
                    var gameexe = Utilities.PromptForGameExecutable(new[] { Game });
                    if (gameexe == null)
                    {
                        return;
                    }
                    targetToBackup = new GameTarget(Game, Utilities.GetGamePathFromExe(Game, gameexe), false, true);
                    if (AvailableBackupSources.Any(x => x.TargetPath.Equals(targetToBackup.TargetPath, StringComparison.InvariantCultureIgnoreCase)))
                    {
                        // Can't point to an existing modding target
                        Log.Error(@"This target is not valid to point to as a backup: It is listed a modding target already, it must be removed as a target first");
                        M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_dialog_linkFailedAlreadyATarget), M3L.GetString(M3L.string_cannotLinkGameCopy), MessageBoxButton.OK, MessageBoxImage.Error);
                        return;
                    }

                    var validationFailureReason = targetToBackup.ValidateTarget(ignoreCmmVanilla: true);
                    if (!targetToBackup.IsValid)
                    {
                        Log.Error(@"This installation is not valid to point to as a backup: " + validationFailureReason);
                        M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_dialog_linkFailedInvalidTarget, validationFailureReason), M3L.GetString(M3L.string_invalidGameCopy), MessageBoxButton.OK, MessageBoxImage.Error);
                        return;
                    }
                }

                NamedBackgroundWorker nbw = new NamedBackgroundWorker(Game + @"Backup");

                nbw.WorkerReportsProgress = true;
                nbw.ProgressChanged      += (a, b) =>
                {
                    if (b.UserState is double d)
                    {
                        window.TaskBarItemInfoHandler.ProgressValue = d;
                    }
                    else if (b.UserState is TaskbarItemProgressState tbs)
                    {
                        window.TaskBarItemInfoHandler.ProgressState = tbs;
                    }
                };
                nbw.DoWork += (a, b) =>
                {
                    Log.Information(@"Starting the backup thread. Checking path: " + targetToBackup.TargetPath);
                    BackupInProgress = true;
                    bool end = false;

                    List <string> nonVanillaFiles = new List <string>();

                    void nonVanillaFileFoundCallback(string filepath)
                    {
                        Log.Error($@"Non-vanilla file found: {filepath}");
                        nonVanillaFiles.Add(filepath);
                    }

                    List <string> inconsistentDLC = new List <string>();

                    void inconsistentDLCFoundCallback(string filepath)
                    {
                        if (targetToBackup.Supported)
                        {
                            Log.Error($@"DLC is in an inconsistent state: {filepath}");
                            inconsistentDLC.Add(filepath);
                        }
                        else
                        {
                            Log.Error(@"Detected an inconsistent DLC, likely due to an unofficial copy of the game");
                        }
                    }

                    ProgressVisible       = true;
                    ProgressIndeterminate = true;
                    BackupStatus          = M3L.GetString(M3L.string_validatingBackupSource);
                    Log.Information(@"Checking target is vanilla");
                    bool isVanilla = VanillaDatabaseService.ValidateTargetAgainstVanilla(targetToBackup, nonVanillaFileFoundCallback);

                    Log.Information(@"Checking DLC consistency");
                    bool isDLCConsistent = VanillaDatabaseService.ValidateTargetDLCConsistency(targetToBackup, inconsistentDLCCallback: inconsistentDLCFoundCallback);

                    Log.Information(@"Checking only vanilla DLC is installed");
                    List <string> dlcModsInstalled = VanillaDatabaseService.GetInstalledDLCMods(targetToBackup).Select(x =>
                    {
                        var tpmi = ThirdPartyServices.GetThirdPartyModInfo(x, targetToBackup.Game);
                        if (tpmi != null)
                        {
                            return($@"{x} ({tpmi.modname})");
                        }
                        return(x);
                    }).ToList();
                    List <string> installedDLC   = VanillaDatabaseService.GetInstalledOfficialDLC(targetToBackup);
                    List <string> allOfficialDLC = MEDirectories.OfficialDLC(targetToBackup.Game);

                    if (installedDLC.Count() < allOfficialDLC.Count())
                    {
                        var dlcList = string.Join("\n - ", allOfficialDLC.Except(installedDLC).Select(x => $@"{MEDirectories.OfficialDLCNames(targetToBackup.Game)[x]} ({x})")); //do not localize
                        dlcList = @" - " + dlcList;
                        Log.Information(@"The following dlc will be missing in the backup if user continues: " + dlcList);

                        Application.Current.Dispatcher.Invoke(delegate
                        {
                            var cancelDueToNotAllDLC = M3L.ShowDialog(window, M3L.GetString(M3L.string_dialog_notAllDLCInstalled, dlcList), M3L.GetString(M3L.string_someDlcNotInstalled), MessageBoxButton.YesNo, MessageBoxImage.Warning);
                            if (cancelDueToNotAllDLC == MessageBoxResult.No)
                            {
                                end = true;
                                EndBackup();
                                return;
                            }
                        });
                    }

                    if (end)
                    {
                        return;
                    }
                    if (isVanilla && isDLCConsistent && dlcModsInstalled.Count == 0)
                    {
                        BackupStatus = M3L.GetString(M3L.string_waitingForUserInput);

                        string backupPath = null;
                        if (!targetToBackup.IsCustomOption)
                        {
                            // Creating a new backup
                            nbw.ReportProgress(0, TaskDialogProgressBarState.Paused);

                            Application.Current.Dispatcher.Invoke(delegate
                            {
                                Log.Information(@"Prompting user to select backup destination");

                                CommonOpenFileDialog m = new CommonOpenFileDialog
                                {
                                    IsFolderPicker   = true,
                                    EnsurePathExists = true,
                                    Title            = M3L.GetString(M3L.string_selectBackupDestination)
                                };
                                if (m.ShowDialog() == CommonFileDialogResult.Ok)
                                {
                                    backupPath = m.FileName;
                                    Log.Information(@"Backup path chosen: " + backupPath);

                                    bool okToBackup = validateBackupPath(backupPath, targetToBackup);
                                    if (!okToBackup)
                                    {
                                        end = true;
                                        EndBackup();
                                        return;
                                    }
                                }
                                else
                                {
                                    end = true;
                                    EndBackup();
                                    return;
                                }
                            });
                            if (end)
                            {
                                return;
                            }
                            nbw.ReportProgress(0, TaskbarItemProgressState.Indeterminate);
                        }
                        else
                        {
                            Log.Information(@"Linking existing backup at " + targetToBackup.TargetPath);
                            backupPath = targetToBackup.TargetPath;
                            // Linking existing backup
                            Application.Current.Dispatcher.Invoke(delegate
                            {
                                bool okToBackup = validateBackupPath(targetToBackup.TargetPath, targetToBackup);
                                if (!okToBackup)
                                {
                                    end = true;
                                    EndBackup();
                                    return;
                                }
                            });
                        }

                        if (end)
                        {
                            return;
                        }

                        if (!targetToBackup.IsCustomOption)
                        {
                            #region callbacks and copy code

                            // Copy to new backup
                            void fileCopiedCallback()
                            {
                                ProgressValue++;
                                if (ProgressMax > 0)
                                {
                                    nbw.ReportProgress(0, ProgressValue * 1.0 / ProgressMax);
                                }
                            }

                            string dlcFolderpath   = MEDirectories.DLCPath(targetToBackup) + '\\';
                            int    dlcSubStringLen = dlcFolderpath.Length;

                            bool aboutToCopyCallback(string file)
                            {
                                try
                                {
                                    if (file.Contains(@"\cmmbackup\"))
                                    {
                                        return(false);                               //do not copy cmmbackup files
                                    }
                                    if (file.StartsWith(dlcFolderpath, StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        //It's a DLC!
                                        string dlcname             = file.Substring(dlcSubStringLen);
                                        var    dlcFolderNameEndPos = dlcname.IndexOf('\\');
                                        if (dlcFolderNameEndPos > 0)
                                        {
                                            dlcname = dlcname.Substring(0, dlcFolderNameEndPos);
                                            if (MEDirectories.OfficialDLCNames(targetToBackup.Game)
                                                .TryGetValue(dlcname, out var hrName))
                                            {
                                                BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX,
                                                                                  hrName);
                                            }
                                            else
                                            {
                                                BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX,
                                                                                  dlcname);
                                            }
                                        }
                                        else
                                        {
                                            // Loose files in the DLC folder
                                            BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX,
                                                                              M3L.GetString(M3L.string_basegame));
                                        }
                                    }
                                    else
                                    {
                                        //It's basegame
                                        if (file.EndsWith(@".bik"))
                                        {
                                            BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX,
                                                                              M3L.GetString(M3L.string_movies));
                                        }
                                        else if (new FileInfo(file).Length > 52428800)
                                        {
                                            BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX,
                                                                              Path.GetFileName(file));
                                        }
                                        else
                                        {
                                            BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX,
                                                                              M3L.GetString(M3L.string_basegame));
                                        }
                                    }
                                }
                                catch (Exception e)
                                {
                                    Crashes.TrackError(e, new Dictionary <string, string>()
                                    {
                                        { @"dlcFolderpath", dlcFolderpath },
                                        { @"dlcSubStringLen", dlcSubStringLen.ToString() },
                                        { @"file", file }
                                    });
                                }

                                return(true);
                            }

                            void totalFilesToCopyCallback(int total)
                            {
                                ProgressValue         = 0;
                                ProgressIndeterminate = false;
                                ProgressMax           = total;
                                nbw.ReportProgress(0, TaskbarItemProgressState.Normal);
                            }

                            BackupStatus = M3L.GetString(M3L.string_creatingBackup);
                            Log.Information($@"Backing up {targetToBackup.TargetPath} to {backupPath}");
                            nbw.ReportProgress(0, TaskbarItemProgressState.Normal);
                            CopyDir.CopyAll_ProgressBar(new DirectoryInfo(targetToBackup.TargetPath),
                                                        new DirectoryInfo(backupPath),
                                                        totalItemsToCopyCallback: totalFilesToCopyCallback,
                                                        aboutToCopyCallback: aboutToCopyCallback,
                                                        fileCopiedCallback: fileCopiedCallback,
                                                        ignoredExtensions: new[] { @"*.pdf", @"*.mp3" });
                            #endregion
                        }

                        // Write key
                        switch (Game)
                        {
                        case Mod.MEGame.ME1:
                        case Mod.MEGame.ME2:
                            Utilities.WriteRegistryKey(App.BACKUP_REGISTRY_KEY, Game + @"VanillaBackupLocation",
                                                       backupPath);
                            break;

                        case Mod.MEGame.ME3:
                            Utilities.WriteRegistryKey(App.REGISTRY_KEY_ME3CMM, @"VanillaCopyLocation",
                                                       backupPath);
                            break;
                        }

                        var cmmvanilla = Path.Combine(backupPath, @"cmm_vanilla");
                        if (!File.Exists(cmmvanilla))
                        {
                            Log.Information($@"Writing cmm_vanilla to " + cmmvanilla);
                            File.Create(cmmvanilla).Close();
                        }

                        Log.Information($@"Backup completed.");

                        Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>()
                        {
                            { @"Game", Game.ToString() },
                            { @"Result", @"Success" },
                            { @"Type", targetToBackup.IsCustomOption ? @"Linked" : @"Copy" }
                        });

                        EndBackup();
                        return;
                    }


                    if (!isVanilla)
                    {
                        //Show UI for non vanilla
                        Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>()
                        {
                            { @"Game", Game.ToString() },
                            { @"Result", @"Failure, Game modified" }
                        });
                        b.Result = (nonVanillaFiles, M3L.GetString(M3L.string_cannotBackupModifiedGame),
                                    M3L.GetString(M3L.string_followingFilesDoNotMatchTheVanillaDatabase));
                    }
                    else if (!isDLCConsistent)
                    {
                        Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>()
                        {
                            { @"Game", Game.ToString() },
                            { @"Result", @"Failure, DLC inconsistent" }
                        });
                        if (targetToBackup.Supported)
                        {
                            b.Result = (inconsistentDLC, M3L.GetString(M3L.string_inconsistentDLCDetected),
                                        M3L.GetString(M3L.string_dialogTheFollowingDLCAreInAnInconsistentState));
                        }
                        else
                        {
                            b.Result = (M3L.GetString(M3L.string_inconsistentDLCDetected),
                                        M3L.GetString(M3L.string_inconsistentDLCDetectedUnofficialGame));
                        }
                    }
                    else if (dlcModsInstalled.Count > 0)
                    {
                        Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>()
                        {
                            { @"Game", Game.ToString() },
                            { @"Result", @"Failure, DLC mods found" }
                        });
                        b.Result = (dlcModsInstalled, M3L.GetString(M3L.string_dlcModsAreInstalled),
                                    M3L.GetString(M3L.string_dialogDLCModsWereDetectedCannotBackup));
                    }
                    EndBackup();
                };
                nbw.RunWorkerCompleted += (a, b) =>
                {
                    if (b.Error != null)
                    {
                        Log.Error($@"Exception occured in {nbw.Name} thread: {b.Error.Message}");
                    }
                    window.TaskBarItemInfoHandler.ProgressState = TaskbarItemProgressState.None;
                    if (b.Result is (List <string> listItems, string title, string text))
                    {
                        ListDialog ld = new ListDialog(listItems, title, text, window);
                        ld.Show();
                    }
                    else if (b.Result is (string errortitle, string message))
                    {
                        M3L.ShowDialog(window, message, errortitle, MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                    CommandManager.InvalidateRequerySuggested();
                };
                nbw.RunWorkerAsync();
            }
        //this should be private but no way to test it private for now...

        /// <summary>
        /// Inspects and loads compressed mods from an archive.
        /// </summary>
        /// <param name="filepath">Path of the archive</param>
        /// <param name="addCompressedModCallback">Callback indicating that the mod should be added to the collection of found mods</param>
        /// <param name="currentOperationTextCallback">Callback to tell caller what's going on'</param>
        /// <param name="forcedOverrideData">Override data about archive. Used for testing only</param>
        public static void InspectArchive(string filepath, Action <Mod> addCompressedModCallback = null, Action <Mod> failedToLoadModeCallback = null, Action <string> currentOperationTextCallback = null, string forcedMD5 = null, int forcedSize = -1)
        {
            string     relayVersionResponse = @"-1";
            List <Mod> internalModList      = new List <Mod>(); //internal mod list is for this function only so we don't need a callback to get our list since results are returned immediately
            var        isExe       = filepath.EndsWith(@".exe");
            var        archiveFile = isExe ? new SevenZipExtractor(filepath, InArchiveFormat.Nsis) : new SevenZipExtractor(filepath);

            using (archiveFile)
            {
#if DEBUG
                foreach (var v in archiveFile.ArchiveFileData)
                {
                    Debug.WriteLine($@"{v.FileName} | Index {v.Index} | Size {v.Size}");
                }
#endif
                var moddesciniEntries = new List <ArchiveFileInfo>();
                var sfarEntries       = new List <ArchiveFileInfo>(); //ME3 DLC
                var bioengineEntries  = new List <ArchiveFileInfo>(); //ME2 DLC
                var me2mods           = new List <ArchiveFileInfo>(); //ME2 RCW Mods
                foreach (var entry in archiveFile.ArchiveFileData)
                {
                    string fname = Path.GetFileName(entry.FileName);
                    if (!entry.IsDirectory && fname.Equals(@"moddesc.ini", StringComparison.InvariantCultureIgnoreCase))
                    {
                        moddesciniEntries.Add(entry);
                    }
                    else if (!entry.IsDirectory && fname.Equals(@"Default.sfar", StringComparison.InvariantCultureIgnoreCase))
                    {
                        //for unofficial lookups
                        sfarEntries.Add(entry);
                    }
                    else if (!entry.IsDirectory && fname.Equals(@"BIOEngine.ini", StringComparison.InvariantCultureIgnoreCase))
                    {
                        //for unofficial lookups
                        bioengineEntries.Add(entry);
                    }
                    else if (!entry.IsDirectory && Path.GetExtension(fname) == @".me2mod")
                    {
                        //for unofficial lookups
                        me2mods.Add(entry);
                    }
                }

                if (moddesciniEntries.Count > 0)
                {
                    foreach (var entry in moddesciniEntries)
                    {
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                        Mod m = new Mod(entry, archiveFile);
                        if (m.ValidMod)
                        {
                            addCompressedModCallback?.Invoke(m);
                            internalModList.Add(m);
                        }
                        else
                        {
                            failedToLoadModeCallback?.Invoke(m);
                        }
                    }
                }
                else if (me2mods.Count > 0)
                {
                    //found some .me2mod files.
                    foreach (var entry in me2mods)
                    {
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                        MemoryStream ms = new MemoryStream();
                        archiveFile.ExtractFile(entry.Index, ms);
                        ms.Position = 0;
                        StreamReader reader         = new StreamReader(ms);
                        string       text           = reader.ReadToEnd();
                        var          rcwModsForFile = RCWMod.ParseRCWMods(Path.GetFileNameWithoutExtension(entry.FileName), text);
                        foreach (var rcw in rcwModsForFile)
                        {
                            Mod m = new Mod(rcw);
                            addCompressedModCallback?.Invoke(m);
                            internalModList.Add(m);
                        }
                    }
                }
                else
                {
                    Log.Information(@"Querying third party importing service for information about this file");
                    currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_queryingThirdPartyImportingService));
                    var  md5  = forcedMD5 ?? Utilities.CalculateMD5(filepath);
                    long size = forcedSize > 0 ? forcedSize : new FileInfo(filepath).Length;
                    var  potentialImportinInfos = ThirdPartyServices.GetImportingInfosBySize(size);
                    var  importingInfo          = potentialImportinInfos.FirstOrDefault(x => x.md5 == md5);

                    if (importingInfo == null && isExe)
                    {
                        Log.Error(@"EXE-based mods must be validated by ME3Tweaks before they can be imported into M3. This is to prevent breaking third party mods.");
                        return;
                    }

                    if (importingInfo?.servermoddescname != null)
                    {
                        //Partially supported unofficial third party mod
                        //Mod has a custom written moddesc.ini stored on ME3Tweaks
                        Log.Information(@"Fetching premade moddesc.ini from ME3Tweaks for this mod archive");
                        string custommoddesc    = OnlineContent.FetchThirdPartyModdesc(importingInfo.servermoddescname);
                        Mod    virutalCustomMod = new Mod(custommoddesc, "", archiveFile); //Load virutal mod
                        if (virutalCustomMod.ValidMod)
                        {
                            addCompressedModCallback?.Invoke(virutalCustomMod);
                            internalModList.Add(virutalCustomMod);
                            return; //Don't do further parsing as this is custom written
                        }
                        else
                        {
                            Log.Error(@"Server moddesc was not valid for this mod. This shouldn't occur. Please report to Mgamerz.");
                            return;
                        }
                    }

                    ExeTransform transform = null;
                    if (importingInfo?.exetransform != null)
                    {
                        Log.Information(@"TPIS lists exe transform for this mod: " + importingInfo.exetransform);
                        transform = new ExeTransform(OnlineContent.FetchExeTransform(importingInfo.exetransform));
                    }

                    //Fully unofficial third party mod.

                    //ME3
                    foreach (var sfarEntry in sfarEntries)
                    {
                        var vMod = AttemptLoadVirtualMod(sfarEntry, archiveFile, Mod.MEGame.ME3, md5);
                        if (vMod.ValidMod)
                        {
                            addCompressedModCallback?.Invoke(vMod);
                            internalModList.Add(vMod);
                            vMod.ExeExtractionTransform = transform;
                        }
                    }

                    //TODO: ME2
                    //foreach (var entry in bioengineEntries)
                    //{
                    //    var vMod = AttemptLoadVirtualMod(entry, archiveFile, Mod.MEGame.ME2, md5);
                    //    if (vMod.ValidMod)
                    //    {
                    //        addCompressedModCallback?.Invoke(vMod);
                    //        internalModList.Add(vMod);
                    //    }
                    //}

                    //TODO: ME1

                    if (importingInfo?.version != null)
                    {
                        foreach (Mod compressedMod in internalModList)
                        {
                            compressedMod.ModVersionString = importingInfo.version;
                            Version.TryParse(importingInfo.version, out var parsedValue);
                            compressedMod.ParsedModVersion = parsedValue;
                        }
                    }
                    else if (relayVersionResponse == @"-1")
                    {
                        //If no version information, check ME3Tweaks to see if it's been added recently
                        //see if server has information on version number
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_gettingAdditionalInformationAboutFileFromME3Tweaks));
                        Log.Information(@"Querying ME3Tweaks for additional information");
                        var modInfo = OnlineContent.QueryModRelay(md5, size);
                        //todo: make this work offline.
                        if (modInfo != null && modInfo.TryGetValue(@"version", out string value))
                        {
                            Log.Information(@"ME3Tweaks reports version number for this file is: " + value);
                            foreach (Mod compressedMod in internalModList)
                            {
                                compressedMod.ModVersionString = value;
                                Version.TryParse(value, out var parsedValue);
                                compressedMod.ParsedModVersion = parsedValue;
                            }
                            relayVersionResponse = value;
                        }
                        else
                        {
                            Log.Information(@"ME3Tweaks does not have additional version information for this file");
                            Analytics.TrackEvent("Non Mod Manager Mod Dropped", new Dictionary <string, string>()
                            {
                                { "Filename", Path.GetFileName(filepath) },
                                { "MD5", md5 }
                            });
                        }
                    }

                    else
                    {
                        //Try straight up TPMI import?
                        Log.Warning($@"No importing information is available for file with hash {md5}. No mods could be found.");
                        Analytics.TrackEvent("Non Mod Manager Mod Dropped", new Dictionary <string, string>()
                        {
                            { "Filename", Path.GetFileName(filepath) },
                            { "MD5", md5 }
                        });
                    }
                }
            }
        }
        private void InspectArchiveBackgroundThread(object sender, DoWorkEventArgs e)
        {
            TaskRunning = true;
            ActionText  = M3L.GetString(M3L.string_interp_openingX, ScanningFile);

            var archive = e.Argument as string;

            void AddCompressedModCallback(Mod m)
            {
                Application.Current.Dispatcher.Invoke(delegate
                {
                    CompressedMods.Add(m);
                    if (CompressedMods.Count > 1 && !openedMultipanel)
                    {
                        Storyboard sb = FindResource(@"OpenWebsitePanel") as Storyboard;
                        if (sb.IsSealed)
                        {
                            sb = sb.Clone();
                        }
                        Storyboard.SetTarget(sb, MultipleModsPopupPanel);
                        sb.Begin();
                        openedMultipanel = true;
                    }
                    CompressedMods.Sort(x => x.ModName);
                });
            }

            void CompressedModFailedCallback(Mod m)
            {
                Application.Current.Dispatcher.Invoke(delegate { NoModSelectedText += M3L.GetString(M3L.string_interp_XfailedToLoadY, m.ModName, m.LoadFailedReason); });
            }

            if (Path.GetExtension(archive) == @".me2mod")
            {
                //RCW
                var RCWMods = RCWMod.ParseRCWMods(Path.GetFileNameWithoutExtension(archive), File.ReadAllText(archive));
                foreach (var rcw in RCWMods)
                {
                    AddCompressedModCallback(new Mod(rcw));
                }
                return;
            }
            //Embedded executables.
            var    archiveSize         = new FileInfo(archive).Length;
            var    knownModsOfThisSize = ThirdPartyServices.GetImportingInfosBySize(archiveSize);
            string pathOverride        = null;

            if (knownModsOfThisSize.Count > 0 && knownModsOfThisSize.Any(x => x.zippedexepath != null))
            {
                //might have embedded exe
                if (archive.RepresentsFileArchive())
                {
                    SevenZipExtractor sve             = new SevenZipExtractor(archive);
                    string            embeddedExePath = null;
                    Log.Information(@"This file may contain a known exe-based mod.");
                    foreach (var importingInfo in knownModsOfThisSize)
                    {
                        if (importingInfo.zippedexepath == null)
                        {
                            continue;
                        }
                        if (sve.ArchiveFileNames.Contains(importingInfo.zippedexepath))
                        {
                            embeddedExePath = importingInfo.zippedexepath;
                            //Ensure embedded exe is supported at least by decompressed size
                            var exedata = sve.ArchiveFileData.FirstOrDefault(x => x.FileName == embeddedExePath);
                            if (exedata.FileName != null)
                            {
                                var importingInfo2 = ThirdPartyServices.GetImportingInfosBySize((long)exedata.Size);
                                if (importingInfo2.Count == 0)
                                {
                                    Log.Warning(@"zip wrapper for this file has importing information but the embedded exe does not!");
                                    break; //no importing info
                                }

                                Log.Information(@"Reading embedded executable file in archive: " + embeddedExePath);
                                ActionText          = M3L.GetString(M3L.string_readingZippedExecutable);
                                pathOverride        = Path.Combine(Utilities.GetTempPath(), Path.GetFileName(embeddedExePath));
                                using var outstream = new FileStream(pathOverride, FileMode.Create);
                                sve.Extracting     += (o, pea) => { ActionText = $@"{M3L.GetString(M3L.string_readingZippedExecutable)} {pea.PercentDone}%"; };
                                sve.ExtractFile(embeddedExePath, outstream);
                                ArchiveFilePath = pathOverride; //set new path so further extraction calls use correct archive path.
                                break;
                            }
                        }
                    }
                }
            }


            void ActionTextUpdateCallback(string newText)
            {
                ActionText = newText;
            }

            InspectArchive(pathOverride ?? archive, AddCompressedModCallback, CompressedModFailedCallback, ActionTextUpdateCallback);
        }
Example #7
0
        public void OnTargetModChanged()
        {
            var tpmi = ThirdPartyServices.GetThirdPartyModInfo(TargetMod, EditingMod.Game);

            TPMIModName = tpmi?.modname;
        }
        //this should be private but no way to test it private for now...

        /// <summary>
        /// Inspects and loads compressed mods from an archive.
        /// </summary>
        /// <param name="filepath">Path of the archive</param>
        /// <param name="addCompressedModCallback">Callback indicating that the mod should be added to the collection of found mods</param>
        /// <param name="currentOperationTextCallback">Callback to tell caller what's going on'</param>
        /// <param name="forcedOverrideData">Override data about archive. Used for testing only</param>
        public static void InspectArchive(string filepath, Action <Mod> addCompressedModCallback = null, Action <Mod> failedToLoadModeCallback = null, Action <string> currentOperationTextCallback = null,
                                          Action showALOTLauncher = null, string forcedMD5 = null, int forcedSize = -1)
        {
            string     relayVersionResponse = @"-1";
            List <Mod> internalModList      = new List <Mod>(); //internal mod list is for this function only so we don't need a callback to get our list since results are returned immediately
            var        isExe       = filepath.EndsWith(@".exe");
            var        archiveFile = isExe ? new SevenZipExtractor(filepath, InArchiveFormat.Nsis) : new SevenZipExtractor(filepath);

            using (archiveFile)
            {
#if DEBUG
                foreach (var v in archiveFile.ArchiveFileData)
                {
                    Debug.WriteLine($@"{v.FileName} | Index {v.Index} | Size {v.Size} | Last Modified {v.LastWriteTime}");
                }
#endif
                var  moddesciniEntries = new List <ArchiveFileInfo>();
                var  sfarEntries       = new List <ArchiveFileInfo>(); //ME3 DLC
                var  bioengineEntries  = new List <ArchiveFileInfo>(); //ME2 DLC
                var  me2mods           = new List <ArchiveFileInfo>(); //ME2 RCW Mods
                var  textureModEntries = new List <ArchiveFileInfo>(); //TPF MEM MOD files
                bool isAlotFile        = false;
                try
                {
                    foreach (var entry in archiveFile.ArchiveFileData)
                    {
                        if (!entry.IsDirectory)
                        {
                            string fname = Path.GetFileName(entry.FileName);
                            if (fname.Equals(@"ALOTInstaller.exe", StringComparison.InvariantCultureIgnoreCase))
                            {
                                isAlotFile = true;
                            }
                            else if (fname.Equals(@"moddesc.ini", StringComparison.InvariantCultureIgnoreCase))
                            {
                                moddesciniEntries.Add(entry);
                            }
                            else if (fname.Equals(@"Default.sfar", StringComparison.InvariantCultureIgnoreCase))
                            {
                                //for unofficial lookups
                                sfarEntries.Add(entry);
                            }
                            else if (fname.Equals(@"BIOEngine.ini", StringComparison.InvariantCultureIgnoreCase))
                            {
                                //for unofficial lookups
                                bioengineEntries.Add(entry);
                            }
                            else if (Path.GetExtension(fname) == @".me2mod")
                            {
                                me2mods.Add(entry);
                            }
                            else if (Path.GetExtension(fname) == @".mem" || Path.GetExtension(fname) == @".tpf" || Path.GetExtension(fname) == @".mod")
                            {
                                //for forwarding to ALOT Installer
                                textureModEntries.Add(entry);
                            }
                        }
                    }
                }
                catch (SevenZipArchiveException svae)
                {
                    //error reading archive!
                    Mod failed = new Mod(false);
                    failed.ModName          = M3L.GetString(M3L.string_archiveError);
                    failed.LoadFailedReason = M3L.GetString(M3L.string_couldNotInspectArchive7zException);
                    Log.Error($@"Unable to inspect archive {filepath}: SevenZipException occurred! It may be corrupt. The specific error was: {svae.Message}");
                    failedToLoadModeCallback?.Invoke(failed);
                    addCompressedModCallback?.Invoke(failed);
                    return;
                }

                // Used for TPIS information lookup
                long archiveSize = forcedSize > 0 ? forcedSize : new FileInfo(filepath).Length;

                if (moddesciniEntries.Count > 0)
                {
                    foreach (var entry in moddesciniEntries)
                    {
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                        Mod m = new Mod(entry, archiveFile);
                        if (!m.ValidMod)
                        {
                            failedToLoadModeCallback?.Invoke(m);
                            m.SelectedForImport = false;
                        }

                        addCompressedModCallback?.Invoke(m);
                        internalModList.Add(m);
                    }
                }
                else if (me2mods.Count > 0)
                {
                    //found some .me2mod files.
                    foreach (var entry in me2mods)
                    {
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                        MemoryStream ms = new MemoryStream();
                        archiveFile.ExtractFile(entry.Index, ms);
                        ms.Position = 0;
                        StreamReader reader         = new StreamReader(ms);
                        string       text           = reader.ReadToEnd();
                        var          rcwModsForFile = RCWMod.ParseRCWMods(Path.GetFileNameWithoutExtension(entry.FileName), text);
                        foreach (var rcw in rcwModsForFile)
                        {
                            Mod m = new Mod(rcw);
                            addCompressedModCallback?.Invoke(m);
                            internalModList.Add(m);
                        }
                    }
                }
                else if (textureModEntries.Any() && isAlotFile)
                {
                    if (isAlotFile)
                    {
                        //is alot installer
                        Log.Information(@"This file contains texture files and ALOTInstaller.exe - this is an ALOT main file");
                        var textureLibraryPath = Utilities.GetALOTInstallerTextureLibraryDirectory();
                        if (textureLibraryPath != null)
                        {
                            //we have destination
                            var destPath = Path.Combine(textureLibraryPath, Path.GetFileName(filepath));
                            if (!File.Exists(destPath))
                            {
                                Log.Information(M3L.GetString(M3L.string_thisFileIsNotInTheTextureLibraryMovingItToTheTextureLibrary));
                                currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_movingALOTFileToTextureLibraryPleaseWait));
                                archiveFile.Dispose();
                                File.Move(filepath, destPath, true);
                                showALOTLauncher?.Invoke();
                            }
                        }
                    }
                    //todo: Parse
                    //else
                    //{
                    //    //found some texture-mod only files
                    //    foreach (var entry in textureModEntries)
                    //    {
                    //        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_interp_readingX, entry.FileName));
                    //        MemoryStream ms = new MemoryStream();
                    //        archiveFile.ExtractFile(entry.Index, ms);
                    //        ms.Position = 0;
                    //        StreamReader reader = new StreamReader(ms);
                    //        string text = reader.ReadToEnd();
                    //        var rcwModsForFile = RCWMod.ParseRCWMods(Path.GetFileNameWithoutExtension(entry.FileName), text);
                    //        foreach (var rcw in rcwModsForFile)
                    //        {
                    //            Mod m = new Mod(rcw);
                    //            addCompressedModCallback?.Invoke(m);
                    //            internalModList.Add(m);
                    //        }
                    //    }
                    //}
                }
                else
                {
                    Log.Information(@"Querying third party importing service for information about this file: " + filepath);
                    currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_queryingThirdPartyImportingService));
                    var md5 = forcedMD5 ?? Utilities.CalculateMD5(filepath);
                    var potentialImportinInfos = ThirdPartyServices.GetImportingInfosBySize(archiveSize);
                    var importingInfo          = potentialImportinInfos.FirstOrDefault(x => x.md5 == md5);

                    if (importingInfo == null && isExe)
                    {
                        Log.Error(@"EXE-based mods must be validated by ME3Tweaks before they can be imported into M3. This is to prevent breaking third party mods.");
                        return;
                    }

                    if (importingInfo?.servermoddescname != null)
                    {
                        //Partially supported unofficial third party mod
                        //Mod has a custom written moddesc.ini stored on ME3Tweaks
                        Log.Information(@"Fetching premade moddesc.ini from ME3Tweaks for this mod archive");
                        string custommoddesc    = null;
                        string loadFailedReason = null;
                        try
                        {
                            custommoddesc = OnlineContent.FetchThirdPartyModdesc(importingInfo.servermoddescname);
                        }
                        catch (Exception e)
                        {
                            loadFailedReason = e.Message;
                            Log.Error(@"Error fetching moddesc from server: " + e.Message);
                        }

                        Mod virutalCustomMod = new Mod(custommoddesc, "", archiveFile); //Load virutal mod
                        if (virutalCustomMod.ValidMod)
                        {
                            Log.Information(@"Mod loaded from server moddesc.");
                            addCompressedModCallback?.Invoke(virutalCustomMod);
                            internalModList.Add(virutalCustomMod);
                            return; //Don't do further parsing as this is custom written
                        }
                        else
                        {
                            if (loadFailedReason != null)
                            {
                                virutalCustomMod.LoadFailedReason = M3L.GetString(M3L.string_interp_failedToFetchModdesciniFileFromServerReasonLoadFailedReason, loadFailedReason);
                            }
                            else
                            {
                                Log.Error(@"Server moddesc was not valid for this mod. This shouldn't occur. Please report to Mgamerz.");
                            }
                            return;
                        }
                    }

                    ExeTransform transform = null;
                    if (importingInfo?.exetransform != null)
                    {
                        Log.Information(@"TPIS lists exe transform for this mod: " + importingInfo.exetransform);
                        transform = new ExeTransform(OnlineContent.FetchExeTransform(importingInfo.exetransform));
                    }

                    //Fully unofficial third party mod.

                    //ME3
                    foreach (var sfarEntry in sfarEntries)
                    {
                        var vMod = AttemptLoadVirtualMod(sfarEntry, archiveFile, Mod.MEGame.ME3, md5);
                        if (vMod != null)
                        {
                            addCompressedModCallback?.Invoke(vMod);
                            internalModList.Add(vMod);
                            vMod.ExeExtractionTransform = transform;
                        }
                    }

                    //TODO: ME2 ?
                    //foreach (var entry in bioengineEntries)
                    //{
                    //    var vMod = AttemptLoadVirtualMod(entry, archiveFile, Mod.MEGame.ME2, md5);
                    //    if (vMod.ValidMod)
                    //    {
                    //        addCompressedModCallback?.Invoke(vMod);
                    //        internalModList.Add(vMod);
                    //    }
                    //}

                    //TODO: ME1 ?

                    if (importingInfo?.version != null)
                    {
                        foreach (Mod compressedMod in internalModList)
                        {
                            compressedMod.ModVersionString = importingInfo.version;
                            Version.TryParse(importingInfo.version, out var parsedValue);
                            compressedMod.ParsedModVersion = parsedValue;
                        }
                    }
                    else if (relayVersionResponse == @"-1")
                    {
                        //If no version information, check ME3Tweaks to see if it's been added recently
                        //see if server has information on version number
                        currentOperationTextCallback?.Invoke(M3L.GetString(M3L.string_gettingAdditionalInformationAboutFileFromME3Tweaks));
                        Log.Information(@"Querying ME3Tweaks for additional information for this file...");
                        var modInfo = OnlineContent.QueryModRelay(md5, archiveSize);
                        //todo: make this work offline.
                        if (modInfo != null && modInfo.TryGetValue(@"version", out string value))
                        {
                            Log.Information(@"ME3Tweaks reports version number for this file is: " + value);
                            foreach (Mod compressedMod in internalModList)
                            {
                                compressedMod.ModVersionString = value;
                                Version.TryParse(value, out var parsedValue);
                                compressedMod.ParsedModVersion = parsedValue;
                            }
                            relayVersionResponse = value;
                        }
                        else
                        {
                            Log.Information(@"ME3Tweaks does not have additional version information for this file.");
                            Analytics.TrackEvent(@"Non Mod Manager Mod Dropped", new Dictionary <string, string>()
                            {
                                { @"Filename", Path.GetFileName(filepath) },
                                { @"MD5", md5 }
                            });
                            foreach (Mod compressedMod in internalModList)
                            {
                                compressedMod.ModVersionString = M3L.GetString(M3L.string_unknown);
                            }
                        }
                    }

                    else
                    {
                        //Try straight up TPMI import?
                        Log.Warning($@"No importing information is available for file with hash {md5}. No mods could be found.");
                        Analytics.TrackEvent(@"Non Mod Manager Mod Dropped", new Dictionary <string, string>()
                        {
                            { @"Filename", Path.GetFileName(filepath) },
                            { @"MD5", md5 }
                        });
                    }
                }
            }
        }
 public InstalledOfficialDLC(string foldername, bool installed, MEGame game)
 {
     FolderName = foldername;
     Installed  = installed;
     HumanName  = ThirdPartyServices.GetThirdPartyModInfo(FolderName, game)?.modname ?? foldername;
 }
Example #10
0
        private void PerformSearch()
        {
            Results.ClearEx();
            var searchGames = new List <string>();

            if (SearchME1)
            {
                searchGames.Add(@"masseffect");
            }
            if (SearchME2)
            {
                searchGames.Add(@"masseffect2");
            }
            if (SearchME3)
            {
                searchGames.Add(@"masseffect3");
            }
            QueryInProgress = true;
            try
            {
                foreach (var domain in searchGames)
                {
                    if (!LoadedDatabases.TryGetValue(domain, out var db))
                    {
                        db = GameDatabase.LoadDatabase(domain);
                        if (db != null)
                        {
                            LoadedDatabases[domain] = db;
                        }
                    }

                    // Check if the name exists in filenames. If it doesn't, it will never find it
#if DEBUG
                    var ignoredItems = new List <string>()
                    {
                        @"DLC_MOD_FMRM_Patches",
                        @"DLC_MOD_FJRM_Patches",
                        @"DLC_ASH_MiniSkirt_Mods",
                        @"DLC_Explorer",
                        @"DLC_LIA_RA4_MeshOnly",
                        @"DLC_ASH_Shorts_Mod",
                        @"DLC_ASH_Alt_Mods",
                        @"DLC_ASH_Socks_Mod",
                        @"DLC_ASH_Topless_Mod",
                        @"DLC_GAR_FRM_Altered_Face_Legs_Mod",
                        @"DLC_GAR_GFC_Altered_Face_Legs_Mod",
                        @"DLC_LIA_NKDSlippers_Mod",
                        @"DLC_LIA_NKDSnickers_Mod",
                        @"DLC_GAR_GFC_New_Version",
                        @"DLC_GAR_GFC_Old_Version",
                        @"DLC_GAR_FRM_Textures",
                        @"DLC_MIR_Shorts_Mod",
                        @"DLC_MOD_IT_RUS",
                    };
                    var dlcNames = db.NameTable.Values.Where(x => !ignoredItems.Contains(x) && x.StartsWith(@"DLC_") && Path.GetExtension(x) == string.Empty && !x.Contains(" ") && ThirdPartyServices.GetThirdPartyModInfo(x, MEGame.ME3) == null).Select(x => x.Trim()).Distinct().ToList();
                    var xx       = new List <string>();
                    foreach (var i in db.FileInstances.Values)
                    {
                        foreach (var f in i)
                        {
                            if (f.ParentPathID > 0)
                            {
                                var path = db.Paths[f.ParentPathID].GetFullPath(db);
                                if (path.ContainsAny(dlcNames, StringComparison.Ordinal))
                                {
                                    var finfo = $@"https://nexusmods.com/masseffect/mods/{f.ModID}";
                                    xx.Add(db.NameTable[db.ModFileInfos[f.FileID].NameID] + " " + finfo);
                                }
                            }
                        }
                    }
                    File.WriteAllLines(@"D:\dlcNames.txt", dlcNames);
                    File.WriteAllLines(@"D:\mods.txt", xx);
#endif
                    var match = db.NameTable.FirstOrDefault(x =>
                                                            x.Value.Equals(SearchTerm, StringComparison.InvariantCultureIgnoreCase));

                    if (match.Key != 0)
                    {
                        // Found
                        var instances = db.FileInstances[match.Key];
                        Results.AddRange(instances.Select(x => new SearchedItemResult()
                        {
                            Instance     = x,
                            Domain       = domain,
                            Filename     = db.NameTable[x.FilenameId],
                            AssociatedDB = db
                        }));
                    }
                }

                StatusText      = M3L.GetString(M3L.string_interp_resultsCount, Results.Count);
                QueryInProgress = false;
            }
            catch (Exception e)
            {
                Log.Error($@"Could not perform search: {e.Message}");
                QueryInProgress = false;
            }
        }
Example #11
0
        private void InspectArchiveBackgroundThread(object sender, DoWorkEventArgs e)
        {
            TaskRunning = true;
            ActionText  = $"Opening {ScanningFile}";

            var archive = e.Argument as string;

            //Embedded executables.
            var    archiveSize         = new FileInfo(archive).Length;
            var    knownModsOfThisSize = ThirdPartyServices.GetImportingInfosBySize(archiveSize);
            string pathOverride        = null;

            if (knownModsOfThisSize.Count > 0 && knownModsOfThisSize.Any(x => x.zippedexepath != null))
            {
                //might have embedded exe
                if (archive.RepresentsFileArchive())
                {
                    SevenZipExtractor sve             = new SevenZipExtractor(archive);
                    string            embeddedExePath = null;
                    Log.Information("This file may contain a known exe-based mod.");
                    foreach (var importingInfo in knownModsOfThisSize)
                    {
                        if (importingInfo.zippedexepath == null)
                        {
                            continue;
                        }
                        if (sve.ArchiveFileNames.Contains(importingInfo.zippedexepath))
                        {
                            embeddedExePath = importingInfo.zippedexepath;
                            //Ensure embedded exe is supported at least by decompressed size
                            var exedata = sve.ArchiveFileData.FirstOrDefault(x => x.FileName == embeddedExePath);
                            if (exedata.FileName != null)
                            {
                                var importingInfo2 = ThirdPartyServices.GetImportingInfosBySize((long)exedata.Size);
                                if (importingInfo2.Count == 0)
                                {
                                    Log.Warning("zip wrapper for this file has importing information but the embedded exe does not!");
                                    break; //no importing info
                                }

                                Log.Information("Reading embedded executable file in archive: " + embeddedExePath);
                                ActionText          = "Reading zipped executable";
                                pathOverride        = Path.Combine(Utilities.GetTempPath(), Path.GetFileName(embeddedExePath));
                                using var outstream = new FileStream(pathOverride, FileMode.Create);
                                sve.Extracting     += (o, pea) => { ActionText = $"Reading zipped executable {pea.PercentDone}%"; };
                                sve.ExtractFile(embeddedExePath, outstream);
                                ArchiveFilePath = pathOverride; //set new path so further extraction calls use correct archive path.
                                break;
                            }
                        }
                    }
                }
            }

            void AddCompressedModCallback(Mod m)
            {
                Application.Current.Dispatcher.Invoke(delegate
                {
                    CompressedMods.Add(m);
                    CompressedMods.Sort(x => x.ModName);
                });
            }

            void CompressedModFailedCallback(Mod m)
            {
                Application.Current.Dispatcher.Invoke(delegate { NoModSelectedText += $"\n\n{m.ModName} failed to load: {m.LoadFailedReason}"; });
            }

            void ActionTextUpdateCallback(string newText)
            {
                ActionText = newText;
            }

            InspectArchive(pathOverride ?? archive, AddCompressedModCallback, CompressedModFailedCallback, ActionTextUpdateCallback);
        }
        public static bool RunPlotManagerUpdate(GameTarget target)
        {
            Log.Information($@"Updating PlotManager for game: {target.TargetPath}");
            var supercedances = M3Directories.GetFileSupercedances(target, new[] { @".pmu" });
            Dictionary <string, string> funcMap = new();
            List <string> combinedNames         = new List <string>();

            if (supercedances.TryGetValue(@"PlotManagerUpdate.pmu", out var supercedanes))
            {
                supercedanes.Reverse(); // list goes from highest to lowest. We want to build in lowest to highest
                StringBuilder sb             = null;
                string        currentFuncNum = null;
                var           metaMaps       = M3Directories.GetMetaMappedInstalledDLC(target, false);
                foreach (var pmuDLCName in supercedanes)
                {
                    var uiName = metaMaps[pmuDLCName]?.ModName ?? ThirdPartyServices.GetThirdPartyModInfo(pmuDLCName, target.Game)?.modname ?? pmuDLCName;
                    combinedNames.Add(uiName);
                    var text = File.ReadAllLines(Path.Combine(M3Directories.GetDLCPath(target), pmuDLCName, target.Game.CookedDirName(), @"PlotManagerUpdate.pmu"));
                    foreach (var line in text)
                    {
                        if (line.StartsWith(@"public function bool F"))
                        {
                            if (sb != null)
                            {
                                funcMap[currentFuncNum] = sb.ToString();
                                Log.Information($@"PlotSync: Adding function {currentFuncNum} from {pmuDLCName}");
                                currentFuncNum = null;
                            }

                            sb = new StringBuilder();
                            sb.AppendLine(line);

                            // Method name
                            currentFuncNum = line.Substring(22);
                            currentFuncNum = currentFuncNum.Substring(0, currentFuncNum.IndexOf('('));
                            if (int.TryParse(currentFuncNum, out var num))
                            {
                                if (num <= 0)
                                {
                                    Log.Error($@"Skipping plot manager update: Conditional {num} is not a valid number for use. Values must be greater than 0 and less than 2 billion.");
                                    Analytics.TrackEvent(@"Bad plot manager function", new Dictionary <string, string>()
                                    {
                                        { @"FunctionName", $@"F{currentFuncNum}" },
                                        { @"DLCName", pmuDLCName }
                                    });
                                    sb = null;
                                    return(false);
                                }
                                else if (num.ToString().Length != currentFuncNum.Length)
                                {
                                    Log.Error($@"Skipping plot manager update: Conditional {currentFuncNum} is not a valid number for use. Values must not contain leading zeros");
                                    Analytics.TrackEvent(@"Bad plot manager function", new Dictionary <string, string>()
                                    {
                                        { @"FunctionName", $@"F{currentFuncNum}" },
                                        { @"DLCName", pmuDLCName }
                                    });
                                    sb = null;
                                    return(false);
                                }
                            }
                            else
                            {
                                Log.Error($@"Skipping plot manager update: Conditional {currentFuncNum} is not a valid number for use. Values must be greater than 0 and less than 2 billion.");
                                Analytics.TrackEvent(@"Bad plot manager function", new Dictionary <string, string>()
                                {
                                    { @"FunctionName", $@"F{currentFuncNum}" },
                                    { @"DLCName", pmuDLCName }
                                });
                                sb = null;
                                return(false);
                            }
                        }
                        else
                        {
                            sb?.AppendLine(line);
                        }
                    }

                    // Add final, if any was found
                    if (sb != null)
                    {
                        funcMap[currentFuncNum] = sb.ToString();
                        Log.Information($@"PlotSync: Adding function {currentFuncNum} from {pmuDLCName}");
                    }
                }
            }

            var pmPath = GetPlotManagerPath(target);
            var vpm    = Utilities.ExtractInternalFileToStream($@"MassEffectModManagerCore.modmanager.plotmanager.{target.Game}.PlotManager.{(target.Game == MEGame.ME1 ? @"u" : @"pcc")}"); // do not localize

            if (funcMap.Any())
            {
                var plotManager      = MEPackageHandler.OpenMEPackageFromStream(vpm, $@"PlotManager.{(target.Game == MEGame.ME1 ? @"u" : @"pcc")}"); // do not localize
                var clonableFunction = plotManager.Exports.FirstOrDefault(x => x.ClassName == @"Function");

                // STEP 1: ADD ALL NEW FUNCTIONS BEFORE WE INITIALIZE THE FILELIB.
                foreach (var v in funcMap)
                {
                    var pmKey = $@"BioAutoConditionals.F{v.Key}";
                    var exp   = plotManager.FindExport(pmKey);
                    if (exp == null)
                    {
                        // Adding a new conditional
                        exp            = EntryCloner.CloneEntry(clonableFunction);
                        exp.ObjectName = new NameReference($@"F{v.Key}", 0);
                        exp.FileRef.InvalidateLookupTable(); // We changed the name.

                        // Reduces trash
                        UFunction uf = ObjectBinary.From <UFunction>(exp);
                        uf.Children    = 0;
                        uf.ScriptBytes = Array.Empty <byte>(); // No script data
                        exp.WriteBinary(uf);
                        Log.Information($@"Generated new blank conditional function export: {exp.UIndex} {exp.InstancedFullPath}", Settings.LogModInstallation);
                    }
                }

                // Relink child chain
                UClass uc = ObjectBinary.From <UClass>(plotManager.FindExport(@"BioAutoConditionals"));
                uc.UpdateChildrenChain();
                uc.UpdateLocalFunctions();
                uc.Export.WriteBinary(uc);


                // STEP 2: UPDATE FUNCTIONS
                Stopwatch sw          = Stopwatch.StartNew();
                var       fl          = new FileLib(plotManager);
                bool      initialized = fl.Initialize(new RelativePackageCache()
                {
                    RootPath = M3Directories.GetBioGamePath(target)
                }, target.TargetPath, canUseBinaryCache: false);
                if (!initialized)
                {
                    Log.Error(@"Error initializing FileLib for plot manager sync:");
                    foreach (var v in fl.InitializationLog.AllErrors)
                    {
                        Log.Error(v.Message);
                    }
                    throw new Exception(M3L.GetString(M3L.string_interp_fileLibInitFailedPlotManager, string.Join(Environment.NewLine, fl.InitializationLog.AllErrors.Select(x => x.Message)))); //force localize
                }
                sw.Stop();
                Debug.WriteLine($@"Took {sw.ElapsedMilliseconds}ms to load filelib");

                bool relinkChain = false;
                foreach (var v in funcMap)
                {
                    var pmKey = $@"BioAutoConditionals.F{v.Key}";
                    Log.Information($@"Updating conditional entry: {pmKey}", Settings.LogModInstallation);
                    var exp = plotManager.FindExport(pmKey);
                    (_, MessageLog log) = UnrealScriptCompiler.CompileFunction(exp, v.Value, fl);
                    if (log.AllErrors.Any())
                    {
                        Log.Error($@"Error compiling function {exp.InstancedFullPath}:");
                        foreach (var l in log.AllErrors)
                        {
                            Log.Error(l.Message);
                        }

                        throw new Exception(M3L.GetString(M3L.string_interp_errorCompilingFunctionReason, exp, string.Join('\n', log.AllErrors.Select(x => x.Message))));
                    }
                }

                if (plotManager.IsModified)
                {
                    plotManager.Save(pmPath, true);
                    // Update local file DB
                    var bgfe = new BasegameFileIdentificationService.BasegameCloudDBFile(pmPath.Substring(target.TargetPath.Length + 1), (int)new FileInfo(pmPath).Length, target.Game, M3L.GetString(M3L.string_interp_plotManagerSyncForX, string.Join(@", ", combinedNames)), Utilities.CalculateMD5(pmPath));
                    BasegameFileIdentificationService.AddLocalBasegameIdentificationEntries(new List <BasegameFileIdentificationService.BasegameCloudDBFile>(new[] { bgfe }));
                }
            }
            else
            {
                // Just write out vanilla.
                vpm.WriteToFile(pmPath);
            }

            return(true);
        }