Пример #1
0
        public static void MWLoadTest()
        {
            string fileESM = "C:\\Software\\Steam\\steamapps\\common\\Morrowind\\Data Files\\Morrowind.esm";
            //string BM = "C:\\Software\\Steam\\steamapps\\common\\Morrowind\\Data Files\\Bloodmoon.esm";
            var  timer = Stopwatch.StartNew();
            TES3 tes3  = TES3.TES3Load(fileESM, new List <string> {
                "LAND", "LTEX"
            });
            //TES3 bm = TES3.TES3Load(BM, new List<string> { "LAND","LTEX" });

            //tes3.TES3Save("C:/mapstuff/out.esp");

            var heightmap = new TES3HeightMap(tes3);

            var options = new ExportOptions()
            {
                HeightMap = true, VertexColorMap = true, TexturePlacementMap = true, ExportHeightAsRaw = true
            };

            heightmap.ReadMapData(@"C:/mapstuff/output", options);


            //heightmap.ImportMapFromImage("C:/mapstuff/output");

            //var test = new TES3HeightMap();
            //test.ImportMapFromImage("C:/mapstuff/SEWorld", -50, -20);

            //var options2 = new ExportOptions() { HeightMap = true, VertexColorMap = false, TexturePlacementMap = false, ExportHeightAsRaw = false };
            //heightmap.ReadMapData(@"C:/mapstuff/output", options2);
            timer.Stop();

            Console.WriteLine($"Done in {timer.ElapsedMilliseconds} ms");
        }
Пример #2
0
        static void Main(string[] args)
        {
#if DEBUG
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
#endif

            // Create our log.
            Logger = new StreamWriter("TES3Merge.log", false)
            {
                AutoFlush = true
            };

            var version = Assembly.GetExecutingAssembly().GetName().Version;
            Logger.WriteLine($"TES3Merge v0.5.");

            // Main execution attempt.
#if DEBUG == false
            try
#endif
            {
                // Load this application's configuration.
                {
                    var    parser  = new FileIniDataParser();
                    string iniPath = $"{AppDomain.CurrentDomain.BaseDirectory}\\TES3Merge.ini";
                    Configuration = parser.ReadFile(iniPath);
                }

                // Determine what encoding to use.
                try
                {
                    var iniEncodingCode = Configuration["General"]["TextEncodingCode"];
                    if (int.TryParse(iniEncodingCode, out int newEncodingCode))
                    {
                        // TODO: Check a list of supported encoding codes.
                        if (newEncodingCode != 932 && (newEncodingCode < 1250 || newEncodingCode > 1252))
                        {
                            throw new Exception($"Encoding code '{newEncodingCode}' is not supported. See TES3Merge.ini for supported values.");
                        }

                        var encoding = Encoding.GetEncoding(newEncodingCode);
                        Logger.WriteLine($"Using encoding: {encoding.EncodingName}");
                        Utility.Common.TextEncodingCode = newEncodingCode;
                    }
                    else
                    {
                        throw new Exception($"Encoding code '{iniEncodingCode}' is not a valid integer. See TES3Merge.ini for supported values.");
                    }
                }
                catch (Exception e)
                {
                    // Write the exception as a warning and set the default Windows-1252 encoding.
                    WriteToLogAndConsole($"WARNING: Could not resolve default text encoding code: {e.Message}");
                    Console.WriteLine("Default encoding of Windows-1252 (English) will be used.");
                    Utility.Common.TextEncodingCode = 1252;
                }

                // Find out where Morrowind lives.
                string morrowindPath = GetMorrowindFolder();
                if (morrowindPath == null)
                {
                    WriteToLogAndConsole("ERROR: Could not resolve Morrowind directory. Install TES3Merge folder into the Morrowind installation folder.");
                }
                Logger.WriteLine($"Morrowind found at '{morrowindPath}'.");

                // Create our merged object TES3 file.
                TES3 mergedObjects       = new TES3();
                var  mergedObjectsHeader = new TES3Lib.Records.TES3
                {
                    HEDR = new TES3Lib.Subrecords.TES3.HEDR()
                    {
                        CompanyName = "TES3Merge",
                        Description = $"Automatic merge generated at {DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")}.",
                        Version     = 1.3f,
                    }
                };
                mergedObjects.Records.Add(mergedObjectsHeader);

                // Get a list of supported mergable object types.
                List <string> supportedMergeTags = new List <string>
                {
                    "ACTI",
                    "ALCH",
                    "APPA",
                    "ARMO",
                    "BODY",
                    "BOOK",
                    "BSGN",
                    //"CELL",
                    "CLAS",
                    "CLOT",
                    "CONT",
                    "CREA",
                    //"DIAL",
                    "DOOR",
                    "ENCH",
                    "FACT",
                    //"GLOB",
                    "GMST",
                    //"INFO",
                    "INGR",
                    //"LAND",
                    //"LEVC",
                    //"LEVI",
                    "LIGH",
                    "LOCK",
                    //"LTEX",
                    "MGEF",
                    "MISC",
                    "NPC_",
                    //"PGRD",
                    "PROB",
                    //"RACE",
                    //"REFR",
                    //"REGN",
                    "REPA",
                    //"SCPT",
                    "SKIL",
                    "SNDG",
                    "SOUN",
                    "SPEL",
                    "STAT",
                    "WEAP",
                };

                // Allow INI to remove types from merge.
                foreach (var recordTypeConfig in Configuration["RecordTypes"])
                {
                    bool.TryParse(recordTypeConfig.Value, out bool supported);
                    if (!supported)
                    {
                        supportedMergeTags.Remove(recordTypeConfig.KeyName);
                    }
                }
                Logger.WriteLine($"Supported record types: {string.Join(", ", supportedMergeTags)}");

                // Get object ID filtering from INI.
                List <KeyValuePair <string, bool> > objectIdFilters = new List <KeyValuePair <string, bool> >();
                foreach (var kv in Configuration["ObjectFilters"])
                {
                    bool.TryParse(kv.Value, out bool allow);
                    objectIdFilters.Add(new KeyValuePair <string, bool>(kv.KeyName.Trim('"'), allow));
                }

                // Collections for managing our objects.
                Dictionary <string, Dictionary <string, List <TES3Lib.Base.Record> > > recordOverwriteMap = new Dictionary <string, Dictionary <string, List <TES3Lib.Base.Record> > >();

                // Get the game file list from the ini file.
                List <string>             sortedMasters              = new List <string>();
                Dictionary <TES3, string> mapTES3ToFileNames         = new Dictionary <TES3, string>();
                Dictionary <TES3Lib.Base.Record, TES3> recordMasters = new Dictionary <TES3Lib.Base.Record, TES3>();
                Console.WriteLine("Parsing content files...");
                {
                    // Try to get INI information.
                    IniData data;
                    try
                    {
                        var parser = new FileIniDataParser();
                        data = parser.ReadFile($"{morrowindPath}\\Morrowind.ini");
                    }
                    catch (Exception firstTry)
                    {
                        try
                        {
                            // Try again with invalid line skipping.
                            var parser = new FileIniDataParser();
                            var config = parser.Parser.Configuration;
                            config.SkipInvalidLines       = true;
                            config.AllowDuplicateKeys     = true;
                            config.AllowDuplicateSections = true;
                            data = parser.ReadFile($"{morrowindPath}\\Morrowind.ini");

                            // If the first pass fails, be more forgiving, but let the user know their INI has issues.
                            Console.WriteLine("WARNING: Issues were found with your Morrowind.ini file. See TES3Merge.log for details.");
                            Logger.WriteLine($"WARNING: Could not parse Morrowind.ini with initial pass. Error: {firstTry.Message}");
                        }
                        catch (Exception secondTry)
                        {
                            Console.WriteLine("ERROR: Unrecoverable issues were found with your Morrowind.ini file. See TES3Merge.log for details.");
                            Logger.WriteLine($"ERROR: Could not parse Morrowind.ini with second pass. Error: {secondTry.Message}");

                            ShowCompletionPrompt();
                            return;
                        }
                    }

                    // Build a list of activated files.
                    HashSet <string> activatedMasters = new HashSet <string>();
                    for (int i = 0; i < 255; i++)
                    {
                        string gameFile = data["Game Files"]["GameFile" + i];
                        if (gameFile == null)
                        {
                            break;
                        }

                        if (gameFile == "Merged_Objects.esp" || gameFile == "Merged Objects.esp")
                        {
                            continue;
                        }

                        activatedMasters.Add(gameFile);
                    }

                    // Add all ESM files first, then ESP files.
                    foreach (var path in Directory.GetFiles($"{morrowindPath}\\Data Files", "*.esm", SearchOption.TopDirectoryOnly).OrderBy(p => File.GetLastWriteTime(p).Ticks))
                    {
                        var fileName = Path.GetFileName(path);
                        if (activatedMasters.Contains(fileName))
                        {
                            sortedMasters.Add(fileName);
                        }
                    }
                    foreach (var path in Directory.GetFiles($"{morrowindPath}\\Data Files", "*.esp", SearchOption.TopDirectoryOnly).OrderBy(p => File.GetLastWriteTime(p).Ticks))
                    {
                        var fileName = Path.GetFileName(path);
                        if (activatedMasters.Contains(fileName))
                        {
                            sortedMasters.Add(fileName);
                        }
                    }

                    // Go through and build a record list.
                    foreach (var sortedMaster in sortedMasters)
                    {
                        string fullGameFilePath = $"{morrowindPath}\\Data Files\\{sortedMaster}";
                        var    lastWriteTime    = File.GetLastWriteTime(fullGameFilePath);
                        Logger.WriteLine($"Parsing input file: {sortedMaster} @ {lastWriteTime}");
                        TES3 file = TES3.TES3Load(fullGameFilePath, supportedMergeTags);
                        mapTES3ToFileNames[file] = sortedMaster;

                        foreach (var record in file.Records)
                        {
                            if (record == null)
                            {
                                continue;
                            }

                            if (record.GetType().Equals(typeof(TES3Lib.Records.TES3)))
                            {
                                continue;
                            }

                            string editorId = record.GetEditorId().Replace("\0", string.Empty);
                            if (string.IsNullOrEmpty(editorId))
                            {
                                continue;
                            }

                            // Check against object filters.
                            bool   allow   = true;
                            string lowerId = editorId.ToLower();
                            foreach (var kv in objectIdFilters)
                            {
                                try
                                {
                                    if (Regex.Match(lowerId, kv.Key).Success)
                                    {
                                        allow = kv.Value;
                                    }
                                }
                                catch (Exception)
                                {
                                }
                            }
                            if (!allow)
                            {
                                continue;
                            }

                            if (!recordOverwriteMap.ContainsKey(record.Name))
                            {
                                recordOverwriteMap[record.Name] = new Dictionary <string, List <TES3Lib.Base.Record> >();
                            }

                            var map = recordOverwriteMap[record.Name];
                            if (!map.ContainsKey(editorId))
                            {
                                map[editorId] = new List <TES3Lib.Base.Record>();
                            }

                            map[editorId].Add(record);
                            recordMasters[record] = file;
                        }
                    }
                }

                // Check to see if we have any potential merges.
                if (recordMasters.Count == 0)
                {
                    WriteToLogAndConsole("No potential record merges found. Aborting.");
                    ShowCompletionPrompt();
                    return;
                }

                // Go through and build merged objects.
                bool.TryParse(Configuration["General"]["DumpMergedRecordsToLog"], out bool dumpMergedRecordsToLog);
                Console.WriteLine("Building merges...");
                HashSet <string> usedMasters = new HashSet <string>();
                foreach (var recordType in recordOverwriteMap.Keys)
                {
                    var recordsMap = recordOverwriteMap[recordType];
                    foreach (string id in recordsMap.Keys)
                    {
                        var records = recordsMap[id];
                        if (records.Count > 2)
                        {
                            var firstRecord = records[0];
                            var lastRecord  = records.Last();
                            var firstMaster = mapTES3ToFileNames[recordMasters[firstRecord]];
                            var lastMaster  = mapTES3ToFileNames[recordMasters[lastRecord]];

                            HashSet <string> localUsedMasters = new HashSet <string>()
                            {
                                firstMaster, lastMaster
                            };

                            var lastSerialized            = lastRecord.GetRawLoadedBytes();
                            TES3Lib.Base.Record newRecord = Activator.CreateInstance(lastRecord.GetType(), new object[] { lastSerialized }) as TES3Lib.Base.Record;
                            for (int i = records.Count - 2; i > 0; i--)
                            {
                                var record = records[i];
                                var master = mapTES3ToFileNames[recordMasters[record]];
                                if (newRecord.MergeWith(record, firstRecord))
                                {
                                    localUsedMasters.Add(master);
                                }
                            }

                            var newSerialized = newRecord.SerializeRecord();
                            if (!lastSerialized.SequenceEqual(newSerialized))
                            {
                                Console.WriteLine($"Merged {newRecord.Name} record: {id}");
                                mergedObjects.Records.Add(newRecord);

                                foreach (string master in localUsedMasters)
                                {
                                    usedMasters.Add(master);
                                }

                                string masterList = string.Join(", ", GetFilteredLoadList(sortedMasters, localUsedMasters).ToArray());
                                Logger.WriteLine($"Resolved conflicts for {firstRecord.Name} record '{id}' from mods: {masterList}");

                                if (dumpMergedRecordsToLog)
                                {
                                    foreach (var record in records)
                                    {
                                        var master = mapTES3ToFileNames[recordMasters[record]];
                                        Logger.WriteLine($">> {master}: {BitConverter.ToString(record.GetRawLoadedBytes()).Replace("-", "")}");
                                    }
                                    Logger.WriteLine($">> Merged Objects.esp: {BitConverter.ToString(newSerialized).Replace("-", "")}");
                                }
                            }
                        }
                    }
                }

                // Did we even merge anything?
                if (usedMasters.Count == 0)
                {
                    WriteToLogAndConsole("No merges were deemed necessary. Aborting.");
                    ShowCompletionPrompt();
                    return;
                }

                // Add the necessary masters.
                Logger.WriteLine("Saving Merged Objects.esp ...");
                mergedObjectsHeader.Masters = new List <(TES3Lib.Subrecords.TES3.MAST MAST, TES3Lib.Subrecords.TES3.DATA DATA)>();
                foreach (var gameFile in GetFilteredLoadList(sortedMasters, usedMasters))
                {
                    if (usedMasters.Contains(gameFile))
                    {
                        long size = new FileInfo($"{morrowindPath}\\Data Files\\{gameFile}").Length;
                        mergedObjectsHeader.Masters.Add((new TES3Lib.Subrecords.TES3.MAST {
                            Filename = $"{gameFile}\0"
                        }, new TES3Lib.Subrecords.TES3.DATA {
                            MasterDataSize = size
                        }));
                    }
                }

                // Save out the merged objects file.
                mergedObjectsHeader.HEDR.NumRecords = mergedObjects.Records.Count - 1;
                mergedObjects.TES3Save(morrowindPath + "\\Data Files\\Merged Objects.esp");
                Logger.WriteLine($"Wrote {mergedObjects.Records.Count - 1} merged objects.");

                ShowCompletionPrompt();
            }
#if DEBUG == false
            catch (Exception e)
            {
                Console.WriteLine("A serious error has occurred. Please post the TES3Merge.log file to GitHub: https://github.com/NullCascade/TES3Merge/issues");
                Logger.WriteLine("An unhandled exception has occurred. Traceback:");
                Logger.WriteLine(e.Message);
                Logger.WriteLine(e.StackTrace);
                ShowCompletionPrompt();
            }
#endif
        }
Пример #3
0
    /// <summary>
    /// Verifies all active esps in the current Morrowind directory
    /// Parses all enabled records of the plugin and checks paths if the file exists
    /// </summary>
    /// <exception cref="Exception"></exception>
    private static void Verify()
    {
        ArgumentNullException.ThrowIfNull(CurrentInstallation);

        using var ssw = new ScopedStopwatch();
        LoadConfig();
        ArgumentNullException.ThrowIfNull(Configuration);

        // get merge tags
        var(supportedMergeTags, objectIdFilters) = GetMergeTags();

        // Shorthand install access.
        var sortedMasters = CurrentInstallation.GameFiles;

        // Go through and build a record list.
        var reportDict = new ConcurrentDictionary <string, Dictionary <string, List <string> > >();

        WriteToLogAndConsole($"Parsing plugins ... ");
        //foreach (var sortedMaster in sortedMasters)
        Parallel.ForEach(sortedMasters, sortedMaster =>
        {
            // this can be enabled actually
            if (Path.GetExtension(sortedMaster) == ".esm")
            {
                //continue;
                return;
            }

            var map = new Dictionary <string, List <string> >();

            // go through all records
            WriteToLogAndConsole($"Parsing input file: {sortedMaster}");
            var fullGameFilePath = Path.Combine(CurrentInstallation.RootDirectory, "Data Files", $"{sortedMaster}");
            var file             = TES3.TES3Load(fullGameFilePath, supportedMergeTags);
            foreach (var record in file.Records)
            {
                #region checks

                if (record is null)
                {
                    continue;
                }
                if (record.GetType().Equals(typeof(TES3Lib.Records.TES3)))
                {
                    continue;
                }
                var editorId = record.GetEditorId().Replace("\0", string.Empty);
                if (string.IsNullOrEmpty(editorId))
                {
                    continue;
                }

                // Check against object filters.
                var allow   = true;
                var lowerId = editorId.ToLower();
                foreach (var kv in objectIdFilters)
                {
                    try
                    {
                        if (Regex.Match(lowerId, kv.Key).Success)
                        {
                            allow = kv.Value;
                        }
                    }
                    catch (Exception)
                    {
                    }
                }
                if (!allow)
                {
                    continue;
                }

                #endregion

                // verify here
                GetPathsInRecord(record, map);
            }

            if (map.Count > 0)
            {
                reportDict.AddOrUpdate(sortedMaster, map, (key, oldValue) => map);
            }
        }
                         );

        // pretty print
        WriteToLogAndConsole($"\n------------------------------------");
        WriteToLogAndConsole($"Results:\n");
        foreach (var(plugin, val) in reportDict)
        {
            WriteToLogAndConsole($"\n{plugin} ({val.Count})");
            foreach (var(recordID, list) in val)
            {
                foreach (var item in list)
                {
                    //Console.WriteLine("{0,-20} {1,5}\n", "Name", "Hours");
                    WriteToLogAndConsole(string.Format("\t{0,-40} {1,5}", recordID, item));
                }
            }
        }
        // serialize to file
        WriteToLogAndConsole($"\n");
        var reportPath = Path.Combine(CurrentInstallation.RootDirectory, "Data Files", "report.json");
        WriteToLogAndConsole($"Writing report to: {reportPath}");
        {
            using var fs = new FileStream(reportPath, FileMode.Create);
            JsonSerializer.Serialize(fs, reportDict, new JsonSerializerOptions()
            {
                WriteIndented = true
            });
        }
    }