示例#1
0
        private void URLValidation(DeploymentChecklistItem obj)
        {
            bool OK = Uri.TryCreate(ModBeingDeployed.ModWebsite, UriKind.Absolute, out var uriResult) &&
                      (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);

            if (!OK)
            {
                obj.HasError   = true;
                obj.Icon       = FontAwesomeIcon.TimesCircle;
                obj.Foreground = Brushes.Red;
                string url = ModBeingDeployed.ModWebsite ?? @"null";
                obj.Errors   = new List <string>(new[] { M3L.GetString(M3L.string_interp_urlIsNotValid, url) });
                obj.ItemText = M3L.GetString(M3L.string_emptyOrInvalidModUrl);
                obj.ToolTip  = M3L.GetString(M3L.string_validationFailed);
            }
            else
            {
                if (ModBeingDeployed.ModWebsite == Mod.DefaultWebsite)
                {
                    obj.Icon       = FontAwesomeIcon.TimesCircle;
                    obj.Foreground = Brushes.Red;
                    obj.ItemText   = "moddesc.ini is missing modsite descriptor";
                    obj.Spinning   = false;
                    obj.HasError   = true;
                    obj.Errors.Add("No mod website URL was set - typically the ModInfo modsite descriptor should point to NexusMods page. This also enables users to endorse your mod on NexusMods through Mod Manager.");
                }
                else
                {
                    obj.Icon       = FontAwesomeIcon.CheckCircle;
                    obj.Foreground = Brushes.Green;
                    obj.ItemText   = M3L.GetString(M3L.string_interp_modURLOK, ModBeingDeployed.ModWebsite);
                    obj.ToolTip    = M3L.GetString(M3L.string_validationOK);
                }
            }
        }
示例#2
0
        private void URLValidation(DeploymentChecklistItem obj)
        {
            bool OK = Uri.TryCreate(ModBeingDeployed.ModWebsite, UriKind.Absolute, out var uriResult) &&
                      (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);

            if (!OK)
            {
                obj.HasError   = true;
                obj.Icon       = FontAwesomeIcon.TimesCircle;
                obj.Foreground = Brushes.Red;
                string url = ModBeingDeployed.ModWebsite ?? @"null";
                obj.Errors   = new List <string>(new[] { M3L.GetString(M3L.string_interp_urlIsNotValid, url) });
                obj.ItemText = M3L.GetString(M3L.string_emptyOrInvalidModUrl);
                obj.ToolTip  = M3L.GetString(M3L.string_validationFailed);
            }
            else
            {
                if (ModBeingDeployed.ModWebsite == Mod.DefaultWebsite)
                {
                    obj.Icon       = FontAwesomeIcon.TimesCircle;
                    obj.Foreground = Brushes.Red;
                    obj.ItemText   = M3L.GetString(M3L.string_moddescMissingModsite);
                    obj.Spinning   = false;
                    obj.HasError   = true;
                    obj.Errors.Add(M3L.GetString(M3L.string_noModWebsiteSet));
                }
                else
                {
                    obj.Icon       = FontAwesomeIcon.CheckCircle;
                    obj.Foreground = Brushes.Green;
                    obj.ItemText   = M3L.GetString(M3L.string_interp_modURLOK, ModBeingDeployed.ModWebsite);
                    obj.ToolTip    = M3L.GetString(M3L.string_validationOK);
                }
            }
        }
        private void URLValidation(DeploymentChecklistItem obj)
        {
            bool OK = Uri.TryCreate(ModBeingDeployed.ModWebsite, UriKind.Absolute, out var uriResult) &&
                      (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);

            if (!OK)
            {
                obj.HasError   = true;
                obj.Icon       = FontAwesomeIcon.TimesCircle;
                obj.Foreground = Brushes.Red;
                obj.Errors     = new List <string>(new[] { $"The URL ({ModBeingDeployed.ModWebsite ?? "null"}) is not a valid URL. Update the modsite descriptor in moddesc.ini to point to your mod's page so users can easily find your mod after import." });
                obj.ItemText   = "Empty or invalid mod URL";
                obj.ToolTip    = "Validation failed";
            }
            else
            {
                obj.Icon       = FontAwesomeIcon.CheckCircle;
                obj.Foreground = Brushes.Green;
                obj.ItemText   = "Mod URL OK: " + ModBeingDeployed.ModWebsite;
                obj.ToolTip    = "Validation OK";
            }
        }
示例#4
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;
        }
示例#5
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();
        }
示例#6
0
 private void ManualValidation(DeploymentChecklistItem item)
 {
     item.Foreground = Brushes.Gray;
     item.Icon       = FontAwesomeIcon.CheckCircle;
     item.ToolTip    = M3L.GetString(M3L.string_thisItemMustBeManuallyCheckedByYou);
 }
示例#7
0
        private void CheckModSFARs(DeploymentChecklistItem item)
        {
            bool hasError = false;

            item.HasError = false;
            item.ItemText = M3L.GetString(M3L.string_checkingSFARFilesSizes);
            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>();

            bool hasSFARs = false;

            foreach (var f in referencedFiles)
            {
                if (_closed)
                {
                    return;
                }
                if (Path.GetExtension(f) == @".sfar")
                {
                    hasSFARs = true;
                    if (new FileInfo(f).Length != 32)
                    {
                        {
                            hasError        = true;
                            item.Icon       = FontAwesomeIcon.TimesCircle;
                            item.Foreground = Brushes.Red;
                            item.Spinning   = false;
                            errors.Add(f);
                        }
                    }
                }
            }

            if (!hasSFARs)
            {
                item.Foreground = Brushes.Green;
                item.Icon       = FontAwesomeIcon.CheckCircle;
                item.ItemText   = M3L.GetString(M3L.string_modDoesNotUseSFARs);
                item.ToolTip    = M3L.GetString(M3L.string_validationOK);
            }
            else
            {
                if (!hasError)
                {
                    item.Foreground = Brushes.Green;
                    item.Icon       = FontAwesomeIcon.CheckCircle;
                    item.ItemText   = M3L.GetString(M3L.string_noSFARSizeIssuesWereDetected);
                    item.ToolTip    = M3L.GetString(M3L.string_validationOK);
                }
                else
                {
                    item.Errors   = errors;
                    item.ItemText = M3L.GetString(M3L.string_someSFARSizesAreTheIncorrectSize);
                    item.ToolTip  = M3L.GetString(M3L.string_validationFailed);
                }

                item.HasError = hasError;
            }
        }
示例#8
0
        private void CheckLocalizationsME3(DeploymentChecklistItem obj)
        {
            var customDLCJob     = ModBeingDeployed.GetJob(ModJob.JobHeader.CUSTOMDLC);
            var customDLCFolders = customDLCJob.CustomDLCFolderMapping.Keys.ToList();

            customDLCFolders.AddRange(customDLCJob.AlternateDLCs.Where(x => x.Operation == AlternateDLC.AltDLCOperation.OP_ADD_CUSTOMDLC).Select(x => x.AlternateDLCFolder));
            var           languages = StarterKitGeneratorWindow.me3languages;
            List <string> errors    = new List <string>();

            obj.ItemText = M3L.GetString(M3L.string_languageCheckInProgress);
            foreach (var customDLC in customDLCFolders)
            {
                if (_closed)
                {
                    return;
                }
                var tlkBasePath = Path.Combine(ModBeingDeployed.ModPath, customDLC, @"CookedPCConsole", customDLC);
                Dictionary <string, List <TalkFileME1.TLKStringRef> > tlkMappings = new Dictionary <string, List <TalkFileME1.TLKStringRef> >();
                foreach (var language in languages)
                {
                    if (_closed)
                    {
                        return;
                    }
                    var tlkLangPath = tlkBasePath + @"_" + language.filecode + @".tlk";
                    if (File.Exists(tlkLangPath))
                    {
                        //inspect
                        TalkFileME2ME3 tf = new TalkFileME2ME3();
                        tf.LoadTlkData(tlkLangPath);
                        tlkMappings[language.filecode] = tf.StringRefs;
                    }
                    else
                    {
                        errors.Add(M3L.GetString(M3L.string_interp_customDLCMissingLocalizedTLK, customDLC, language.filecode));;
                    }
                }
                if (tlkMappings.Count > 1)
                {
                    //find TLK with most entries
                    //var tlkCounts = tlkMappings.Select(x => (x.Key, x.Value.Count));
                    double numLoops = Math.Pow(tlkMappings.Count - 1, tlkMappings.Count - 1);
                    int    numDone  = 0;
                    foreach (var mapping1 in tlkMappings)
                    {
                        foreach (var mapping2 in tlkMappings)
                        {
                            if (mapping1.Equals(mapping2))
                            {
                                continue;
                            }

                            var differences = mapping1.Value.Select(x => x.StringID).Except(mapping2.Value.Select(x => x.StringID));
                            foreach (var difference in differences)
                            {
                                var str = mapping1.Value.FirstOrDefault(x => x.StringID == difference)?.Data ?? M3L.GetString(M3L.string_errorFindingString);
                                errors.Add(M3L.GetString(M3L.string_interp_tlkDifference, difference.ToString(), mapping1.Key, mapping2.Key, str));
                            }

                            numDone++;
                            double percent = (numDone * 100.0) / numLoops;
                            obj.ItemText = $@"{M3L.GetString(M3L.string_languageCheckInProgress)} {percent:0.00}%";
                        }
                    }
                    //use INT as master. Not sure if any mods are not-english based
                    //TODO
                }
            }
            if (errors.Count > 0)
            {
                obj.HasError   = true;
                obj.Icon       = FontAwesomeIcon.Warning;
                obj.Foreground = Brushes.Orange;
                obj.Errors     = errors;
                obj.ItemText   = M3L.GetString(M3L.string_languageCheckDetectedIssues);
                obj.ToolTip    = M3L.GetString(M3L.string_validationFailed);
            }
            else
            {
                obj.Icon       = FontAwesomeIcon.CheckCircle;
                obj.Foreground = Brushes.Green;
                obj.ItemText   = M3L.GetString(M3L.string_noLanguageIssuesDetected);
                obj.ToolTip    = M3L.GetString(M3L.string_validationOK);
            }
        }
        private void CheckModForTFCCompactability(DeploymentChecklistItem item)
        {
            // if (ModBeingDeployed.Game >= Mod.MEGame.ME2)
            //{
            bool hasError = false;

            item.HasError = false;
            item.ItemText = "Checking textures in mod";
            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 = $"Checking textures in mod [{numChecked}/{referencedFiles.Count}]";
                if (f.RepresentsPackageFilePath())
                {
                    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)
                                {
                                    hasError        = true;
                                    item.Icon       = FontAwesomeIcon.TimesCircle;
                                    item.Foreground = Brushes.Red;
                                    item.Spinning   = false;
                                    errors.Add($"{texture.FileRef.FilePath} - {texture.GetInstancedFullPath}: Could not load texture data: {e.Message}");
                                }
                            }
                        }
                    }
                }
            }

            if (!hasError)
            {
                item.Foreground = Brushes.Green;
                item.Icon       = FontAwesomeIcon.CheckCircle;
                item.ItemText   = "No broken textures were found";
                item.ToolTip    = "Validation passed";
            }
            else
            {
                item.Errors   = errors;
                item.ItemText = "Texture issues were detected";
                item.ToolTip  = "Validation failed";
            }
            item.HasError = hasError;
            //}
            //} else
            //{
            //    item.Foreground = Brushes.Green;
            //    item.Icon = FontAwesomeIcon.CheckCircle;
            //    item.ItemText = "Textures are not ";
            //    item.ToolTip = "Validation passed";
            //}
        }
 private void ManualValidation(DeploymentChecklistItem item)
 {
     item.Foreground = Brushes.Gray;
     item.Icon       = FontAwesomeIcon.CheckCircle;
     item.ToolTip    = "This item must be manually checked by you";
 }
        private void CheckLocalizationsME3(DeploymentChecklistItem obj)
        {
            var customDLCJob     = ModBeingDeployed.GetJob(ModJob.JobHeader.CUSTOMDLC);
            var customDLCFolders = customDLCJob.CustomDLCFolderMapping.Keys.ToList();

            customDLCFolders.AddRange(customDLCJob.AlternateDLCs.Where(x => x.Operation == AlternateDLC.AltDLCOperation.OP_ADD_CUSTOMDLC).Select(x => x.AlternateDLCFolder));
            var           languages = StarterKitGeneratorWindow.me3languages;
            List <string> errors    = new List <string>();

            obj.ItemText = "Language check in progress";
            foreach (var customDLC in customDLCFolders)
            {
                if (_closed)
                {
                    return;
                }
                var tlkBasePath = Path.Combine(ModBeingDeployed.ModPath, customDLC, "CookedPCConsole", customDLC);
                Dictionary <string, List <TalkFileME1.TLKStringRef> > tlkMappings = new Dictionary <string, List <TalkFileME1.TLKStringRef> >();
                foreach (var language in languages)
                {
                    if (_closed)
                    {
                        return;
                    }
                    var tlkLangPath = tlkBasePath + "_" + language.filecode + ".tlk";
                    if (File.Exists(tlkLangPath))
                    {
                        //inspect
                        TalkFileME2ME3 tf = new TalkFileME2ME3();
                        tf.LoadTlkData(tlkLangPath);
                        tlkMappings[language.filecode] = tf.StringRefs;
                    }
                    else
                    {
                        errors.Add(customDLC + " is missing a localized TLK for language " + language.filecode + ". This DLC will not load if the user's game language is set to this. Some versions of the game cannot have their language changed, so this will effectively lock the user out from using this mod.");
                    }
                }
                if (tlkMappings.Count > 1)
                {
                    //find TLK with most entries
                    //var tlkCounts = tlkMappings.Select(x => (x.Key, x.Value.Count));
                    double numLoops = Math.Pow(tlkMappings.Count - 1, tlkMappings.Count - 1);
                    int    numDone  = 0;
                    foreach (var mapping1 in tlkMappings)
                    {
                        foreach (var mapping2 in tlkMappings)
                        {
                            if (mapping1.Equals(mapping2))
                            {
                                continue;
                            }

                            var differences = mapping1.Value.Select(x => x.StringID).Except(mapping2.Value.Select(x => x.StringID));
                            foreach (var difference in differences)
                            {
                                var str = mapping1.Value.FirstOrDefault(x => x.StringID == difference)?.Data ?? "<error finding string>";
                                errors.Add($"TLKStringID {difference} is present in {mapping1.Key}  but is not present in {mapping2.Key}. Even if this mod is not truly localized to another language, the strings should be copied into other language TLK files to ensure users of that language will see strings instead of string references.\n{str}");
                            }

                            numDone++;
                            double percent = (numDone * 100.0) / numLoops;
                            obj.ItemText = $"Language check in progress {percent:0.00}%";
                        }
                    }
                    //use INT as master. Not sure if any mods are not-english based
                    //TODO
                }
            }
            if (errors.Count > 0)
            {
                obj.HasError   = true;
                obj.Icon       = FontAwesomeIcon.Warning;
                obj.Foreground = Brushes.Orange;
                obj.Errors     = errors;
                obj.ItemText   = "Language check detected issues";
                obj.ToolTip    = "Validation failed";
            }
            else
            {
                obj.Icon       = FontAwesomeIcon.CheckCircle;
                obj.Foreground = Brushes.Green;
                obj.ItemText   = "No language issues detected";
                obj.ToolTip    = "Validation OK";
            }
        }